docs(CLAUDE.md,ARCHITECTURE.md): record snapshot-consumer rollback (e142efd)

CLAUDE.md updates:
- Patterns to use: "useSessionSnapshots" section retitled "(OPT-IN)";
  the documented pilot adopters (useSessionInfo,
  AvatarInfoWidgetAvatarView) are removed. Adds explicit warning about
  the suspected useBetween + useSyncExternalStore + React Compiler
  interaction and the rollback in e142efd.
- Adopted table: snapshot-consumer row changed to "No in-tree
  consumers" with note about defensive fallbacks remaining.
- Not yet table: the useChatWidget reactive-ownUserId line corrected
  to reflect the rollback; the "migrate session-data mirrors" row
  marked BLOCKED with a retry hint (try a non-useBetween consumer
  first to isolate the cause).

ARCHITECTURE.md update:
- useExternalSnapshot bullet in the "Solution" section gains a note
  pointing at the 8 pre-built consumers in useSessionSnapshots.ts and
  the 2026-05-18 rollback caveat with the suspected interaction and
  retry guidance.

Pure documentation refresh; no code change. The useSessionSnapshots.ts
file and the vite alias remain in place — they're not what got rolled
back, only the consumer-side migrations were.
This commit is contained in:
simoleo89
2026-05-18 22:27:09 +02:00
parent e142efd793
commit f4ada81321
2 changed files with 37 additions and 14 deletions
+22 -13
View File
@@ -127,12 +127,12 @@ canonical pattern.
## Patterns to use ## Patterns to use
### `useSessionSnapshots` (renderer snapshot pattern, React-side) ### `useSessionSnapshots` (renderer snapshot pattern, React-side — OPT-IN)
For state that lives on a renderer Manager and is invalidated through For state that lives on a renderer Manager and is invalidated through
`NitroEventType.*_UPDATED`, prefer the snapshot consumer hooks in `NitroEventType.*_UPDATED`, the file
`src/hooks/session/useSessionSnapshots.ts` over `useState + `src/hooks/session/useSessionSnapshots.ts` exposes eight consumer hooks
useMessageEvent` mirrors: backed by `useSyncExternalStore`:
```ts ```ts
const userData = useUserDataSnapshot(); // SessionData const userData = useUserDataSnapshot(); // SessionData
@@ -145,12 +145,21 @@ const vols = useVolumesSnapshot(); // sound volumes
const users = useRoomUserListSnapshot(); // ReadonlyArray<IRoomUserData> const users = useRoomUserListSnapshot(); // ReadonlyArray<IRoomUserData>
``` ```
Each is a thin `useSyncExternalStore` wrapper around the renderer's Each hook has defensive `typeof method === 'function'` guards against
matching `getXxxSnapshot()` + subscription to the matching event. a stale renderer bundle and degrades to a frozen default snapshot if
Snapshot references are renderer-guaranteed stable until invalidation the renderer doesn't expose the matching getter (kept module-level so
React bails out cleanly when nothing changed. Pilot adopters: React's bailout still works on the degraded path).
`useSessionInfo` (userFigure / respects), `AvatarInfoWidgetAvatarView`
(reactive Ignore/Unignore menu entry). **Adoption status: zero in-tree consumers.** The first three pilot
migrations (`useSessionInfo`, `useChatWidget.ownUserId`,
`AvatarInfoWidgetAvatarView` Ignore/Unignore) 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 the defensive guards (`c35a2d4`) could
not eliminate. The hooks remain available for any future opt-in
consumer, but **do not migrate `useBetween`-shared consumers to them
without isolated testing first** — that combination is the suspected
cause of the bug.
### `useNitroEventState` / `useMessageEventState` ### `useNitroEventState` / `useMessageEventState`
@@ -285,7 +294,7 @@ into `configurePreviewServer` so `yarn preview` keeps working.
| Adopted | Pilot sites | | Adopted | Pilot sites |
|---|---| |---|---|
| Renderer snapshot consumer hooks (`useSessionSnapshots`) | `useSessionInfo` (userFigure / respectsLeft / respectsPetLeft via `useUserDataSnapshot`), `AvatarInfoWidgetAvatarView` (reactive Ignore/Unignore via `useIsUserIgnored`). 8 hooks total available; consumers can read userData / activeRoomSession / ignoredUsers / groupBadges / soundVolumes / roomUserList reactively | | Renderer snapshot consumer hooks (`useSessionSnapshots`) | **No in-tree consumers** — three pilot migrations rolled back in `e142efd` due to a runtime `(intermediate value)() is undefined` at `ToolbarView.tsx:46` that survived both the vite-alias fix and defensive guards. The 8 hooks (userData / activeRoomSession / ignoredUsers / groupBadges / soundVolumes / roomUserList / isUserIgnored / groupBadge) remain available as opt-in API with frozen-default fallbacks. |
| `useNitroEventState` + companions (Reducer, ExternalSnapshot) | `OfferView`, `useAvatarInfoWidget` (figure/badges/group reducer), `useInventoryFurni` (pure reducers + fragments useRef) | | `useNitroEventState` + companions (Reducer, ExternalSnapshot) | `OfferView`, `useAvatarInfoWidget` (figure/badges/group reducer), `useInventoryFurni` (pure reducers + fragments useRef) |
| `useNitroQuery` + `useNitroEventInvalidator` | `OfferView`, `CatalogLayoutRoomAdsView`, `ModToolsChatlogView`, `CfhChatlogView`, `useGiftConfiguration`, `useUserGroups`, `useClubOffers(windowId)`, `useSellablePetPalette(breed)`, `useMarketplaceConfiguration`, `useClubGifts` (with invalidator) | | `useNitroQuery` + `useNitroEventInvalidator` | `OfferView`, `CatalogLayoutRoomAdsView`, `ModToolsChatlogView`, `CfhChatlogView`, `useGiftConfiguration`, `useUserGroups`, `useClubOffers(windowId)`, `useSellablePetPalette(breed)`, `useMarketplaceConfiguration`, `useClubGifts` (with invalidator) |
| Zustand | `NavigatorRoomCreatorView` (`useRoomCreatorStore`), `WiredCreatorToolsView` (`useWiredCreatorToolsUiStore` — every panel-lifecycle-relevant flag, snapshot, selection, highlight, inline editor, picker chain hoisted; what's left in the component as `useState` is genuinely transient: keepSelected, globalClock, roomEnteredAt, selectedMonitorErrorType, selectedMonitorLogDetails) | | Zustand | `NavigatorRoomCreatorView` (`useRoomCreatorStore`), `WiredCreatorToolsView` (`useWiredCreatorToolsUiStore` — every panel-lifecycle-relevant flag, snapshot, selection, highlight, inline editor, picker chain hoisted; what's left in the component as `useState` is genuinely transient: keepSelected, globalClock, roomEnteredAt, selectedMonitorErrorType, selectedMonitorLogDetails) |
@@ -298,9 +307,9 @@ into `configurePreviewServer` so `yarn preview` keeps working.
| Not yet | Notes | | Not yet | Notes |
|---|---| |---|---|
| Split `useChatWidget` / `useAvatarInfoWidget` (data/actions) | Both state-driven via events with no clean imperative actions to extract — split still skip-motivated, but each got a targeted tidy in 2026-05-18 (useChatWidget: reactive `ownUserId` via `useUserDataSnapshot`; useAvatarInfoWidget: typed `__nitroAvatarClickControl` accessor + module-scope DEBOUNCE const). Further work would have to be feature-driven. | | Split `useChatWidget` / `useAvatarInfoWidget` (data/actions) | Both state-driven via events with no clean imperative actions to extract — split still skip-motivated, but `useAvatarInfoWidget` got a typed `__nitroAvatarClickControl` accessor + module-scope DEBOUNCE const in 2026-05-18 (commit `05ff7df`). The `useChatWidget` reactive-`ownUserId` migration in the same commit was rolled back in `e142efd`; the hook is back on `GetSessionDataManager()?.userId` (static at mount). |
| Split `usePetPackageWidget` / `useWordQuizWidget` / `useChatCommandSelector` (data/actions) | Data/actions split remains a bad fit, but all three got real modernization in 2026-05-18 instead: usePetPackageWidget → useReducer + extracted `getPetPackageNameError` pure helper + 4 tests; useWordQuizWidget → fixed stale-closure bug in `setUserAnswers` updater + `useRef` for the timeout handle; useChatCommandSelector → module-level `let` cache replaced with a Zustand store. | | Split `usePetPackageWidget` / `useWordQuizWidget` / `useChatCommandSelector` (data/actions) | Data/actions split remains a bad fit, but all three got real modernization in 2026-05-18 instead: usePetPackageWidget → useReducer + extracted `getPetPackageNameError` pure helper + 4 tests; useWordQuizWidget → fixed stale-closure bug in `setUserAnswers` updater + `useRef` for the timeout handle; useChatCommandSelector → module-level `let` cache replaced with a Zustand store. |
| Migrate remaining `useSessionInfo`-style mirrors to renderer snapshots | Pilot done on `useSessionInfo` + `AvatarInfoWidgetAvatarView` + `useChatWidget.ownUserId`. Other candidates: any place that reads `GetSessionDataManager().userId/figure/clubLevel/isModerator` etc. directly in render and never re-renders on session changes. Each is a small migration; no need to bundle. | | Migrate any consumer to renderer snapshot hooks | **Blocked.** Three pilot migrations were rolled back in `e142efd` after the `useSessionInfo` migration triggered a persistent runtime error at `ToolbarView.tsx:46`. The defensive guards in `useSessionSnapshots.ts` and the umbrella vite alias (`790ad2b`) are still in place. Before retrying, isolate the cause: the suspected interaction is `useBetween` + `useSyncExternalStore` + React Compiler. Try a NON-`useBetween` consumer first (e.g. a fresh per-component hook usage in a low-blast-radius widget). |
| Widen the component / hook test coverage | Mock layer is in place (`src/nitro-renderer.mock.ts`) and 3+ hook/component pilots pass. Good follow-up targets: `LoginView` Form Actions happy/error paths, `OfferView` with `useNitroQuery`. (Acceptable only as a side-effect of a real change — coverage growth on its own is deprioritized per session feedback.) | | Widen the component / hook test coverage | Mock layer is in place (`src/nitro-renderer.mock.ts`) and 3+ hook/component pilots pass. Good follow-up targets: `LoginView` Form Actions happy/error paths, `OfferView` with `useNitroQuery`. (Acceptable only as a side-effect of a real change — coverage growth on its own is deprioritized per session feedback.) |
## Known open logic bugs ## Known open logic bugs
+15 -1
View File
@@ -103,7 +103,21 @@ information when forced into a single selector.
`useSyncExternalStore` wrapper pairing the renderer's `useSyncExternalStore` wrapper pairing the renderer's
`EventDispatcher.subscribe()` with the `getXxxSnapshot()` getters `EventDispatcher.subscribe()` with the `getXxxSnapshot()` getters
added in renderer 2.1.0. Use this for readonly views over manager added in renderer 2.1.0. Use this for readonly views over manager
state (`getUserDataSnapshot`, `getActiveRoomSessionSnapshot`). state. Eight pre-built consumers live in
`src/hooks/session/useSessionSnapshots.ts` (userData / activeRoomSession
/ ignoredUsers / groupBadges / soundVolumes / roomUserList + scalar
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.
For state owned outside the listener (the `useState` + `setState(prev => For state owned outside the listener (the `useState` + `setState(prev =>
applyX(prev, event))` pattern), keep using `useNitroEvent` / applyX(prev, event))` pattern), keep using `useNitroEvent` /