From e142efd79389f94e0d00c2c255d8f9ba86909dfe Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Mon, 18 May 2026 22:16:48 +0200 Subject: [PATCH] revert(hooks): roll back the three snapshot-consumer migrations to pre-71a0eee state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. 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. --- .../menu/AvatarInfoWidgetAvatarView.tsx | 10 +--- src/hooks/rooms/widgets/useChatWidget.ts | 9 +-- src/hooks/session/useSessionInfo.ts | 57 ++++++++++++------- 3 files changed, 41 insertions(+), 35 deletions(-) diff --git a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetAvatarView.tsx b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetAvatarView.tsx index f5e362e..582b9a1 100644 --- a/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetAvatarView.tsx +++ b/src/components/room/widgets/avatar-info/menu/AvatarInfoWidgetAvatarView.tsx @@ -3,7 +3,7 @@ import { FC, useEffect, useMemo, useState } from 'react'; import { FaChevronLeft, FaChevronRight } from 'react-icons/fa'; import { AvatarInfoUser, DispatchUiEvent, GetOwnRoomObject, GetUserProfile, LocalizeText, MessengerFriend, ReportType, RoomWidgetUpdateChatInputContentEvent, SendMessageComposer } from '../../../../../api'; import { Flex } from '../../../../../common'; -import { useFriends, useHelp, useIsUserIgnored, useRoom, useSessionInfo, useWiredTools } from '../../../../../hooks'; +import { useFriends, useHelp, useRoom, useSessionInfo, useWiredTools } from '../../../../../hooks'; import { ContextMenuHeaderView } from '../../context-menu/ContextMenuHeaderView'; import { ContextMenuListItemView } from '../../context-menu/ContextMenuListItemView'; import { ContextMenuView } from '../../context-menu/ContextMenuView'; @@ -31,10 +31,6 @@ export const AvatarInfoWidgetAvatarView: FC = p const { roomSession = null, isHandItemBlocked = false } = useRoom(); const { userRespectRemaining = 0, respectUser = null } = useSessionInfo(); const { openInspectionForUser, showInspectButton } = useWiredTools(); - // Reactive: the menu auto-flips Ignore <-> Unignore if the state - // changes while the popup is open, instead of being frozen to the - // snapshot AvatarInfoUtilities took at click time. - const isIgnored = useIsUserIgnored(avatarInfo.name); const isShowGiveRights = useMemo(() => { @@ -235,11 +231,11 @@ export const AvatarInfoWidgetAvatarView: FC = p { LocalizeText('infostand.link.relationship') } } - { !isIgnored && + { !avatarInfo.isIgnored && processAction('ignore') }> { LocalizeText('infostand.button.ignore') } } - { isIgnored && + { avatarInfo.isIgnored && processAction('unignore') }> { LocalizeText('infostand.button.unignore') } } diff --git a/src/hooks/rooms/widgets/useChatWidget.ts b/src/hooks/rooms/widgets/useChatWidget.ts index f81ffd2..f278659 100644 --- a/src/hooks/rooms/widgets/useChatWidget.ts +++ b/src/hooks/rooms/widgets/useChatWidget.ts @@ -1,8 +1,7 @@ -import { GetGuestRoomResultEvent, GetRoomEngine, PetFigureData, RoomChatSettings, RoomChatSettingsEvent, RoomDragEvent, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomSessionChatEvent, RoomUserData, SystemChatStyleEnum } from '@nitrots/nitro-renderer'; +import { GetGuestRoomResultEvent, GetRoomEngine, GetSessionDataManager, PetFigureData, RoomChatSettings, RoomChatSettingsEvent, RoomDragEvent, RoomObjectCategory, RoomObjectType, RoomObjectVariable, RoomSessionChatEvent, RoomUserData, SystemChatStyleEnum } from '@nitrots/nitro-renderer'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { ChatBubbleMessage, ChatBubbleUtilities, ChatEntryType, ChatHistoryCurrentDate, GetConfigurationValue, GetRoomObjectScreenLocation, IRoomChatSettings, LocalizeText, PlaySound, RoomChatFormatter } from '../../../api'; import { useMessageEvent, useNitroEvent } from '../../events'; -import { useUserDataSnapshot } from '../../session/useSessionSnapshots'; import { useTranslation } from '../../translation'; import { useRoom } from '../useRoom'; import { useChatHistory } from './../../chat-history'; @@ -23,11 +22,7 @@ const useChatWidgetState = () => const { addChatEntry, updateChatEntry } = useChatHistory(); const { settings, translateIncoming, consumeOutgoingTranslation } = useTranslation(); const isDisposed = useRef(false); - // Reactive: a session change (re-login without reload) immediately - // updates the outgoing-translation owner check below. Old code read - // GetSessionDataManager()?.userId at hook mount and would have stayed - // stuck on the previous session's id. - const ownUserId = (useUserDataSnapshot().userId || -1); + const ownUserId = (GetSessionDataManager()?.userId || -1); const applyTranslationToBubble = useCallback((chatMessage: ChatBubbleMessage, originalText: string, translatedText: string, detectedLanguage: string, targetLanguage: string) => { diff --git a/src/hooks/session/useSessionInfo.ts b/src/hooks/session/useSessionInfo.ts index bb2cd11..9959698 100644 --- a/src/hooks/session/useSessionInfo.ts +++ b/src/hooks/session/useSessionInfo.ts @@ -1,21 +1,16 @@ -import { GetSessionDataManager, RoomUnitChatStyleComposer, UserSettingsEvent } from '@nitrots/nitro-renderer'; +import { FigureUpdateEvent, GetSessionDataManager, RoomUnitChatStyleComposer, UserInfoDataParser, UserInfoEvent, UserSettingsEvent } from '@nitrots/nitro-renderer'; import { useState } from 'react'; import { useBetween } from 'use-between'; import { SendMessageComposer } from '../../api'; import { useMessageEvent } from '../events'; -import { useUserDataSnapshot } from './useSessionSnapshots'; const useSessionInfoState = () => { - // figure / respectsLeft / respectsPetLeft come from the renderer's - // referentially-stable IUserDataSnapshot. The snapshot is invalidated - // (and re-derived on next read) inside SessionDataManager whenever - // the underlying state changes: UserInfoEvent + FigureUpdateEvent + - // giveRespect/givePetRespect all call invalidateUserDataSnapshot(). - // So we no longer need to mirror those fields into local useState. - const userData = useUserDataSnapshot(); - + const [ userInfo, setUserInfo ] = useState(null); + const [ userFigure, setUserFigure ] = useState(null); const [ chatStyleId, setChatStyleId ] = useState(0); + const [ userRespectRemaining, setUserRespectRemaining ] = useState(0); + const [ petRespectRemaining, setPetRespectRemaining ] = useState(0); const updateChatStyleId = (styleId: number) => { @@ -24,8 +19,36 @@ const useSessionInfoState = () => SendMessageComposer(new RoomUnitChatStyleComposer(styleId)); }; - const respectUser = (userId: number) => GetSessionDataManager().giveRespect(userId); - const respectPet = (petId: number) => GetSessionDataManager().givePetRespect(petId); + const respectUser = (userId: number) => + { + GetSessionDataManager().giveRespect(userId); + + setUserRespectRemaining(GetSessionDataManager().respectsLeft); + }; + + const respectPet = (petId: number) => + { + GetSessionDataManager().givePetRespect(petId); + + setPetRespectRemaining(GetSessionDataManager().respectsPetLeft); + }; + + useMessageEvent(UserInfoEvent, event => + { + const parser = event.getParser(); + + setUserInfo(parser.userInfo); + setUserFigure(parser.userInfo.figure); + setUserRespectRemaining(parser.userInfo.respectsRemaining); + setPetRespectRemaining(parser.userInfo.respectsPetRemaining); + }); + + useMessageEvent(FigureUpdateEvent, event => + { + const parser = event.getParser(); + + setUserFigure(parser.figure); + }); useMessageEvent(UserSettingsEvent, event => { @@ -34,15 +57,7 @@ const useSessionInfoState = () => setChatStyleId(parser.chatType); }); - return { - userFigure: userData.figure, - chatStyleId, - userRespectRemaining: userData.respectsLeft, - petRespectRemaining: userData.respectsPetLeft, - respectUser, - respectPet, - updateChatStyleId - }; + return { userInfo, userFigure, chatStyleId, userRespectRemaining, petRespectRemaining, respectUser, respectPet, updateChatStyleId }; }; export const useSessionInfo = () => useBetween(useSessionInfoState);