mirror of
https://github.com/duckietm/Nitro-V3.git
synced 2026-06-19 15:06:20 +00:00
docs(claude,architecture): refresh snapshot adoption status after 2026-05-19 fix
The earlier "BLOCKED" / "rolled back" framing in CLAUDE.md +
ARCHITECTURE.md is stale: the three pilot snapshot-consumer migrations
shipped in d28819d on 2026-05-19 once the root cause was pinpointed
(`use-between` 1.x ships a dispatcher proxy that doesn't implement
`useSyncExternalStore`, so any snapshot hook called inside
useBetween(stateFn) crashes the first render).
Updated:
- CLAUDE.md → "Patterns to use → useSessionSnapshots": rewrote the
adoption-status paragraph to record the three live consumers, the
hard structural constraint (snapshot reads MUST be outside
useBetween scope, with the precise dispatcher line numbers + the
exact error fingerprint), and the fix template applied to
useSessionInfo (outer wrapper reads the snapshot, inner state
function keeps only use-between-safe hooks).
- CLAUDE.md → "What's wired up and what isn't" tables:
- Adopted row for "Renderer snapshot consumer hooks" lists the
three live consumers instead of the old "No in-tree consumers"
note.
- "Not yet" row renamed from "Blocked" to "Unblocked — migrate more
consumers", with concrete next candidates
(GetSessionDataManager().userId / userName / clubLevel /
securityLevel, GetRoomSessionManager().getActiveSession(),
GetSoundManager().<volume>) and a reminder of the constraint
+ the CI gate that enforces it.
- useChatWidget.ownUserId row notes the reactive migration via
useUserDataSnapshot landed (direct hook call — useChatWidget
isn't wrapped in useBetween, so the constraint doesn't apply).
- ARCHITECTURE.md → "useExternalSnapshot" subsection: replaced the
2026-05-18 rollback note with the structural constraint + the
2026-05-19 fix landing, including pointers to the regression test
and the new CI gate (eslint.hooks.config.mjs + yarn lint:hooks).
No code change in this commit — yarn typecheck clean, yarn
lint:hooks clean.
This commit is contained in:
+30
-9
@@ -109,15 +109,36 @@ information when forced into a single selector.
|
||||
derivations `useIsUserIgnored`, `useGroupBadge`), each with defensive
|
||||
`typeof` guards against a stale renderer bundle.
|
||||
|
||||
**Note (2026-05-18):** the first three pilot migrations (`useSessionInfo`,
|
||||
`useChatWidget.ownUserId`, `AvatarInfoWidgetAvatarView` Ignore-menu)
|
||||
were rolled back in `e142efd` after a persistent runtime error
|
||||
`(intermediate value)() is undefined` at `ToolbarView.tsx:46` that
|
||||
the vite-alias fix (`790ad2b`) and defensive guards (`c35a2d4`) could
|
||||
not eliminate. Suspected interaction: `useBetween` +
|
||||
`useSyncExternalStore` + React Compiler. Before retrying any
|
||||
migration here, exercise the snapshot hooks from a non-`useBetween`
|
||||
consumer in a low-blast-radius widget first to isolate the cause.
|
||||
**Hard constraint — snapshot hooks must run outside `useBetween`.**
|
||||
`use-between` 1.x swaps the React dispatcher with its own proxy
|
||||
(`ownDispatcher` at
|
||||
`node_modules/use-between/release/index.esm.js:54-169`) that
|
||||
reimplements only useState / useReducer / useEffect /
|
||||
useLayoutEffect / useCallback / useMemo / useRef /
|
||||
useImperativeHandle. `useSyncExternalStore` is not on the list, so
|
||||
calling a snapshot hook inside `useBetween(stateFn)` invokes
|
||||
`undefined(...)` and crashes the first render with
|
||||
"(intermediate value)() is undefined" (Firefox) /
|
||||
"dispatcher.useSyncExternalStore is not a function" (Chrome). This
|
||||
is what blocked the original 2026-05-18 migration of
|
||||
`useSessionInfo` — the rollback (`e142efd`) was correct as a stop
|
||||
the bleed, but neither the vite alias (`790ad2b`) nor the
|
||||
defensive renderer-method guards (`c35a2d4`) could address it
|
||||
because both were downstream of the dispatcher proxy.
|
||||
|
||||
**Fix landed 2026-05-19 (`d28819d`).** Three pilot consumers shipped:
|
||||
`useSessionInfo` (snapshot read in the outer wrapper, after
|
||||
`useBetween`); `useChatWidget.ownUserId` (direct hook call —
|
||||
`useChatWidget` is not wrapped in `useBetween`);
|
||||
`AvatarInfoWidgetAvatarView` Ignore/Unignore (direct hook call in a
|
||||
component body via `useIsUserIgnored`). Pattern documented in
|
||||
`CLAUDE.md` under "Patterns to use →
|
||||
`useSessionSnapshots`". Regression guard:
|
||||
`src/hooks/session/useSessionSnapshots.test.tsx` (negative case via
|
||||
`ErrorBoundary` + positive case). CI gate:
|
||||
`yarn lint:hooks` (`eslint.hooks.config.mjs` →
|
||||
`react-hooks/rules-of-hooks: error`) wired into
|
||||
`.github/workflows/ci.yml`.
|
||||
|
||||
For state owned outside the listener (the `useState` + `setState(prev =>
|
||||
applyX(prev, event))` pattern), keep using `useNitroEvent` /
|
||||
|
||||
Reference in New Issue
Block a user