ReviewOS

stacks/stx

useCookie: two unsynced implementations (module composable vs runtime template literal) silently diverge

#1710
Closed glennmichael123 opened this 10 days ago ·
10 days ago

TL;DR

There are two separate useCookie implementations in the codebase:

  1. packages/stx/src/composables/use-cookie.ts:159-272 — TypeScript composable, ~110 lines. Full-featured: JSON serialization, polling-based subscriptions, encode/decode functions, defaultValue resolution, removeCookie helper, clearCookies helper.
  2. Runtime template literal in packages/stx/src/signals.ts — minimal ~30 lines, added under #1701. Regex-based parsing, returns a state() signal, writes via effect(), supports basic options (path/maxAge/sameSite).

Both are exposed as window.stx.useCookie and (probably) import { useCookie } from 'stx'. Whichever the consumer's import path lands on, they get different behavior.

How a user can hit it

// Path A: TypeScript module import (might resolve to use-cookie.ts)
import { useCookie } from '@stacksjs/stx'
const token = useCookie<string>('auth-token')
token.subscribe((next, prev) => {/* fires on polling tick */})

// Path B: from inside a <script client> block (resolves via window.stx)
const token = useCookie('auth-token', { maxAge: 3600 })
// no .subscribe; that's not on the runtime version

Same name, same package, two API surfaces.

Why it's bad

  • Tests targeting one version don't validate the other (the new tests for #1701 only exercise the runtime version)
  • Doc examples written against either version may not work in the other context
  • Feature drift compounds — if someone adds a new feature to one impl, the other doesn't get it until manually mirrored
  • Future "let me improve useCookie" PRs will touch one impl and assume the other follows; it won't

Proposed fix

Pick one canonical source of truth. The pragmatic choice:

  • Module composable wins: signals.ts runtime calls into the composable rather than reimplementing. Requires the composable to be loadable from inside the runtime closure (currently the runtime is generated as a string, so this is non-trivial — would need to inline the composable's source).
  • Or runtime wins: composable becomes a thin re-export of whatever window.stx.useCookie resolves to. Loses tree-shakability for users who only want useCookie server-side.

Either way, the goal is a single behavioral specification and a single set of tests that covers both import paths.

Same pattern likely affects others

Worth checking whether useLocalStorage / useSessionStorage / useEventListener have the same dual-impl pattern. Spot-checking would be useful.

Found during a deep-scan audit.

🤖 Filed via Claude Code

Sign in to comment on this issue.