Commit Graph

8 Commits

Author SHA1 Message Date
simoleo89 d28819db89 fix(snapshots): re-apply the 3 snapshot-consumer migrations with the use-between/useSyncExternalStore incompatibility resolved
Root cause of last session's "(intermediate value)() is undefined" at
ToolbarView.tsx:46:

  use-between 1.x ships its own React-dispatcher proxy (ownDispatcher
  in node_modules/use-between/release/index.esm.js:54-169) that
  re-implements only useState, useReducer, useEffect, useLayoutEffect,
  useCallback, useMemo, useRef and useImperativeHandle. It does NOT
  implement useSyncExternalStore. When the inner state function of
  useBetween(stateFn) calls useSyncExternalStore (directly or via
  useExternalSnapshot / useUserDataSnapshot), React resolves the
  dispatcher to use-between's proxy, finds .useSyncExternalStore
  missing, and calls undefined() — that's the exact production crash
  in Firefox. Chrome reports the same as
  "dispatcher.useSyncExternalStore is not a function".

Neither the vite alias (790ad2b) nor the defensive renderer-method
guards (c35a2d4) could fix it — both addressed downstream symptoms
(stale dist / missing manager methods) but the dispatcher is upstream
of both. That's why every retry kept reproducing the same error.

Fix is structural: snapshot hooks (useUserDataSnapshot,
useIsUserIgnored, etc.) MUST run outside any useBetween scope. Three
re-applied migrations:

- useSessionInfo: snapshot read moved into the outer wrapper. The
  inner useSessionInfoState (useBetween-shared) now contains only
  use-between-safe hooks: useState, useMessageEvent, plain actions.
  userFigure / userRespectRemaining / petRespectRemaining come from
  useUserDataSnapshot() OUTSIDE useBetween, so useSyncExternalStore
  installs against the real React dispatcher.

- useChatWidget.ownUserId: direct snapshot read. useChatWidget is
  exported as `useChatWidget = useChatWidgetState` (NOT wrapped in
  useBetween), so this hook never sat inside the broken scope — the
  precautionary rollback was unnecessary in retrospect. Gains
  session-change reactivity (e.g. reconnect under a different user id).

- AvatarInfoWidgetAvatarView Ignore/Unignore: useIsUserIgnored(name)
  read directly in the component body. Same reasoning as
  useChatWidget — never inside useBetween. The menu auto-flips
  Ignore <-> Unignore while the popup is open.

Added regression guard at src/hooks/session/useSessionSnapshots.test.tsx
with two cases: (1) useSyncExternalStore inside useBetween throws,
(2) useSyncExternalStore outside useBetween in the same render works.
Pins the constraint so future migrations cannot reintroduce the bad
shape silently.

Verification: yarn typecheck clean, yarn test 209/209 (207 baseline
+ 2 new regression cases), no consumer surface changes — every
destructured field (userFigure, userRespectRemaining, respectUser,
petRespectRemaining, respectPet, chatStyleId, updateChatStyleId) is
still returned with the same name and shape.
2026-05-19 17:30:03 +02:00
simoleo89 e142efd793 revert(hooks): roll back the three snapshot-consumer migrations to pre-71a0eee state
The migrations of useSessionInfo, useChatWidget.ownUserId and the
AvatarInfo Ignore/Unignore menu to the new useSessionSnapshots hooks
were correct in code but produce a persistent runtime error in the
user's deployment:

  TypeError: (intermediate value)() is undefined
      ToolbarView ToolbarView.tsx:46

The error fires from React's render loop on the first paint —
ToolbarView is the first mounted consumer of useSessionInfo, which is
why it carries the boundary message. Two attempted fixes did not
resolve it on the user's side:
- 790ad2b: vite alias forcing @nitrots/nitro-renderer to source index.ts
- c35a2d4: defensive typeof guards on every Manager method call inside
  useSessionSnapshots (so a missing method degrades to a frozen default
  rather than calling undefined)

Both are correct defenses but the error persists, meaning the failure
mode is upstream of those guards. Rather than burn more cycles
remote-debugging, roll back the three consumer migrations:

- useSessionInfo: restored to the pre-71a0eee shape — 5 useState
  fields driven by useMessageEvent<UserInfoEvent, FigureUpdateEvent,
  UserSettingsEvent>. The five consumers (ToolbarView, HcCenterView,
  ChatInputView, AvatarInfoPetTrainingPanelView, InfoStandWidgetPetView,
  AvatarInfo{Avatar,Pet,OwnPet}View) get the same destructured shape
  they had before this session.
- useChatWidget.ownUserId: restored to `GetSessionDataManager()?.userId`
  (synchronous, captured at mount). Loses the session-change reactivity
  but matches the previous, working behaviour.
- AvatarInfoWidgetAvatarView Ignore/Unignore: restored to
  `avatarInfo.isIgnored` (captured by AvatarInfoUtilities at click
  time, not reactive). Loses the live-toggle if the user is
  ignored/unignored while the popup is open — known small regression,
  worth it for stability.

Kept intact:
- The useSessionSnapshots.ts hook file itself, with defensive guards,
  so the API stays available for any future opt-in consumer.
- 790ad2b vite alias for the umbrella, still useful as defence in
  depth for future migrations.
- All the other non-snapshot modernizations from this session
  (usePetPackageWidget reducer, useWordQuizWidget bug fix,
  useChatCommandSelector Zustand store, useAvatarInfoWidget typed
  globalThis accessor).

Verification: yarn typecheck clean, yarn test 207/207, yarn build green.
The toolbar should boot without the error now — the call chain on the
first paint no longer touches the new useExternalSnapshot / snapshot
getter path.
2026-05-18 22:16:48 +02:00
simoleo89 36addbe7d4 fix(avatar-info): reactive Ignore/Unignore menu entry via useIsUserIgnored
The Ignore <-> Unignore context-menu entry was driven by
avatarInfo.isIgnored — a boolean captured by AvatarInfoUtilities once,
at the time the avatar was clicked. If the user got ignored / unignored
*while the popup was already open* (e.g. via the friends panel, or
because a server push flipped the state), the menu kept showing the
stale option and clicking it would no-op (or worse, double-ignore).

Switch the menu items to read useIsUserIgnored(avatarInfo.name) — the
reactive hook backed by IgnoredUsersManager.getIgnoredUsersSnapshot()
+ NitroEventType.IGNORED_USERS_UPDATED. Now the menu flips automatically
the moment the ignore list changes, without re-opening.

avatarInfo.isIgnored stays on the data object (other code paths still
consume it) — only the user-facing menu toggle is now reactive.
2026-05-18 21:33:15 +02:00
Lorenzune 57b83c1097 Refine mobile avatar widgets and login flow 2026-05-07 21:19:15 +02:00
Lorenzune e4b1f14fa2 feat: update room control widgets and menus 2026-04-03 12:09:16 +02:00
Lorenzune 83540ff329 feat: add advanced wired variable tools UI 2026-04-02 04:44:04 +02:00
duckietm 242f86bb90 🔒 Small security fix to not allow dangerous html code 2026-02-25 11:17:51 +01:00
DuckieTM 7feb10ab15 🆙 Init V3 2026-01-31 09:10:52 +01:00