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
### `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
`NitroEventType.*_UPDATED`, prefer the snapshot consumer hooks in
`src/hooks/session/useSessionSnapshots.ts` over `useState +
useMessageEvent` mirrors:
`NitroEventType.*_UPDATED`, the file
`src/hooks/session/useSessionSnapshots.ts` exposes eight consumer hooks
backed by `useSyncExternalStore`:
```ts
const userData = useUserDataSnapshot(); // SessionData
@@ -145,12 +145,21 @@ const vols = useVolumesSnapshot(); // sound volumes
const users = useRoomUserListSnapshot(); // ReadonlyArray<IRoomUserData>
```
Each is a thin `useSyncExternalStore` wrapper around the renderer's
matching `getXxxSnapshot()` + subscription to the matching event.
Snapshot references are renderer-guaranteed stable until invalidation
React bails out cleanly when nothing changed. Pilot adopters:
`useSessionInfo` (userFigure / respects), `AvatarInfoWidgetAvatarView`
(reactive Ignore/Unignore menu entry).
Each hook has defensive `typeof method === 'function'` guards against
a stale renderer bundle and degrades to a frozen default snapshot if
the renderer doesn't expose the matching getter (kept module-level so
React's bailout still works on the degraded path).
**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`
@@ -285,7 +294,7 @@ into `configurePreviewServer` so `yarn preview` keeps working.
| 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) |
| `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) |
@@ -298,9 +307,9 @@ into `configurePreviewServer` so `yarn preview` keeps working.
| 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. |
| 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.) |
## Known open logic bugs