Skip to content

fix(createTokens): invalidate resolve cache on every registry mutation#567

Draft
johnleider wants to merge 2 commits into
masterfrom
fix/tokens-cache-invalidation
Draft

fix(createTokens): invalidate resolve cache on every registry mutation#567
johnleider wants to merge 2 commits into
masterfrom
fix/tokens-cache-invalidation

Conversation

@johnleider

Copy link
Copy Markdown
Member

Group B of the createTokens inspection follow-ups — closes the resolve-cache staleness hole (#2). Targeted at v1.0.x (non-breaking runtime fix; public surface unchanged).

The bug

createTokens memoizes resolve() and cleared that cache from seven method wrappers (register/upsert/unregister/onboard/offboard/move/clear). But a ticket's own unregister() is bound to the raw registry method at ticket-creation time, so it bypassed the wrapper entirely:

const tokens = createTokens({ primary: '#007BFF', accent: '{primary}' })
tokens.resolve('accent')            // '#007BFF' (cached)
tokens.get('primary')!.unregister() // self-removal — skipped cache.clear()
tokens.resolve('accent')            // was still '#007BFF'; now undefined

The fix

Build the internal registry with events: true and clear the cache from a single subscription to the five mutation events (register:ticket, unregister:ticket, update:ticket, reindex:registry, clear:registry). Event emission covers every mutation path — including ticket-level self-removal — so the whole bug class is closed, not just the one method. This is the same event set useProxyRegistry subscribes to.

The seven wrappers are deleted; the raw registry methods are exposed through the existing ...registry spread with their full inherited signatures (this also subsumes #564's wrapper-signature alignment — there are no wrappers left to diverge).

Verification

  • pnpm typecheck clean
  • createTokens + useTheme/useFeatures/usePermissions/useLocale suites: 554/554 pass (the consumers all build on createTokens and exercise the shared path)
  • Throwaway probe confirmed the ticket.unregister() scenario above (not committed, per the no-proactive-tests rule) — flagging that feat(group): add new composable #2 has no committed regression test; happy to add one if you want it here.

The 7 cache-clearing method wrappers only covered context-level mutators;
a ticket's own `unregister()` (bound to the raw registry) bypassed them,
leaving stale resolutions in the cache. createTokens now builds its registry
with `events: true` and clears the cache from a single subscription to
register/unregister/update/reindex/clear events, which covers ticket-level
self-removal uniformly. The wrappers are deleted; raw registry methods are
exposed via the existing spread with their full inherited signatures.
@johnleider johnleider added this to the v1.0.x milestone Jul 9, 2026
@johnleider johnleider added T: bug Something isn't working E: createTokens Composable labels Jul 9, 2026
@johnleider johnleider self-assigned this Jul 9, 2026
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Changeset found — this change will be included in the next release. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

E: createTokens Composable T: bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant