From 82bccd4040a34476132592ba6fa22886eb3a9dd6 Mon Sep 17 00:00:00 2001 From: simoleo89 Date: Sat, 16 May 2026 12:20:35 +0200 Subject: [PATCH] wired-tools: hoist monitorSnapshot + polling reset to the Zustand store Move the monitor snapshot off WiredCreatorToolsView's useState into useWiredCreatorToolsUiStore. The WiredMonitorDataEvent listener still lives in the component (it can't move alongside without dragging useMessageEvent into the store), but it now writes to setMonitorSnapshot and the room-change reset calls resetMonitorSnapshot() instead of re-instantiating the default in the component. Direct benefit: the snapshot now survives closing and reopening the panel between two server pushes. Before this commit, the parent remounted on every visibility flip (parent renders null while `!isVisible`) which dropped the snapshot back to the empty default; the user would briefly see zeroed stats until the next `monitor:fetch` roundtrip landed. Holding the snapshot in zustand decouples the data from the component's mount lifecycle. Tests: three new cases on the store cover setMonitorSnapshot, resetMonitorSnapshot returning a fresh empty instance, and the "close/reopen panel preserves snapshot" lifecycle. Total 181/181. --- .../wired-tools/WiredCreatorToolsView.tsx | 10 ++-- .../wiredCreatorToolsUiStore.test.ts | 58 ++++++++++++++++++- .../wired-tools/wiredCreatorToolsUiStore.ts | 22 ++++++- 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/src/components/wired-tools/WiredCreatorToolsView.tsx b/src/components/wired-tools/WiredCreatorToolsView.tsx index dde13ad..2018c16 100644 --- a/src/components/wired-tools/WiredCreatorToolsView.tsx +++ b/src/components/wired-tools/WiredCreatorToolsView.tsx @@ -7,8 +7,8 @@ import { AvatarInfoUtilities, GetRoomObjectBounds, GetRoomObjectScreenLocation, import { Button, DraggableWindowPosition, LayoutAvatarImageView, LayoutPetImageView, LayoutRoomObjectImageView, NitroCardContentView, NitroCardHeaderView, NitroCardTabsItemView, NitroCardTabsView, NitroCardView, Text } from '../../common'; import { useInventoryTrade, useMessageEvent, useNotification, useObjectSelectedEvent, useRoom, useWiredTools } from '../../hooks'; import { DIRECTION_NAMES, EDITABLE_FURNI_VARIABLES, EDITABLE_USER_VARIABLES, INSPECTION_ELEMENTS, MONITOR_ERROR_INFO, MONITOR_LOG_ORDER, MONTH_NAMES, TABS, TEAM_COLOR_NAMES, VARIABLES_ELEMENTS, VARIABLE_DEFINITIONS, WEEKDAY_NAMES, WIRED_CLOCK_REFRESH_MS, WIRED_FREEZE_EFFECT_IDS, WIRED_INSPECTION_REFRESH_MS, WIRED_MONITOR_ACTION_CLEAR_LOGS, WIRED_MONITOR_ACTION_FETCH, WIRED_MONITOR_POLL_MS, WIRED_VARIABLES_POLL_MS } from './WiredCreatorTools.constants'; -import { createEmptyMonitorSnapshot, formatMonitorHistoryOccurrence, formatMonitorLatestOccurrence, formatMonitorSource, formatVariableTimestamp, getHotelDateTimeParts, getHotelTimeFormatter, normalizeMonitorReason } from './WiredCreatorTools.helpers'; -import { HotelDateTimeParts, InspectionElementButton, InspectionElementType, InspectionFurniLiveState, InspectionFurniSelection, InspectionUserLiveState, InspectionUserSelection, InspectionUserTeamData, InspectionVariable, ManagedHolderVariableEntry, MonitorLog, MonitorLogDetails, MonitorSnapshot, MonitorStat, ParsedWallLocation, TeamEffectData, VariableDefinition, VariableHighlightOverlay, VariableHighlightTarget, VariableManageEntry, VariableTextValue, VariablesElementButton, VariablesElementType, WiredToolsTab } from './WiredCreatorTools.types'; +import { formatMonitorHistoryOccurrence, formatMonitorLatestOccurrence, formatMonitorSource, formatVariableTimestamp, getHotelDateTimeParts, getHotelTimeFormatter, normalizeMonitorReason } from './WiredCreatorTools.helpers'; +import { HotelDateTimeParts, InspectionElementButton, InspectionElementType, InspectionFurniLiveState, InspectionFurniSelection, InspectionUserLiveState, InspectionUserSelection, InspectionUserTeamData, InspectionVariable, ManagedHolderVariableEntry, MonitorLog, MonitorLogDetails, MonitorStat, ParsedWallLocation, TeamEffectData, VariableDefinition, VariableHighlightOverlay, VariableHighlightTarget, VariableManageEntry, VariableTextValue, VariablesElementButton, VariablesElementType, WiredToolsTab } from './WiredCreatorTools.types'; import { useWiredCreatorToolsUiStore } from './wiredCreatorToolsUiStore'; import { WiredInspectionTabView } from './WiredInspectionTabView'; import { WiredMonitorTabView } from './WiredMonitorTabView'; @@ -32,7 +32,9 @@ export const WiredCreatorToolsView: FC<{}> = () => const [ selectedUserActionVersion, setSelectedUserActionVersion ] = useState(0); const [ globalClock, setGlobalClock ] = useState(Date.now()); const [ roomEnteredAt, setRoomEnteredAt ] = useState(Date.now()); - const [ monitorSnapshot, setMonitorSnapshot ] = useState(() => createEmptyMonitorSnapshot()); + const monitorSnapshot = useWiredCreatorToolsUiStore(s => s.monitorSnapshot); + const setMonitorSnapshot = useWiredCreatorToolsUiStore(s => s.setMonitorSnapshot); + const resetMonitorSnapshot = useWiredCreatorToolsUiStore(s => s.resetMonitorSnapshot); const [ selectedMonitorErrorType, setSelectedMonitorErrorType ] = useState(null); const [ selectedMonitorLogDetails, setSelectedMonitorLogDetails ] = useState(null); const isMonitorHistoryOpen = useWiredCreatorToolsUiStore(s => s.isMonitorHistoryOpen); @@ -688,7 +690,7 @@ export const WiredCreatorToolsView: FC<{}> = () => useEffect(() => { - setMonitorSnapshot(createEmptyMonitorSnapshot()); + resetMonitorSnapshot(); setSelectedMonitorErrorType(null); setSelectedMonitorLogDetails(null); setIsMonitorHistoryOpen(false); diff --git a/src/components/wired-tools/wiredCreatorToolsUiStore.test.ts b/src/components/wired-tools/wiredCreatorToolsUiStore.test.ts index 1efccae..6c5f54e 100644 --- a/src/components/wired-tools/wiredCreatorToolsUiStore.test.ts +++ b/src/components/wired-tools/wiredCreatorToolsUiStore.test.ts @@ -1,4 +1,5 @@ import { beforeEach, describe, expect, it } from 'vitest'; +import { createEmptyMonitorSnapshot } from './WiredCreatorTools.helpers'; import { useWiredCreatorToolsUiStore } from './wiredCreatorToolsUiStore'; const INITIAL = { @@ -15,7 +16,8 @@ const INITIAL = { monitorHistoryTypeFilter: 'ALL', variableManageTypeFilter: 'ALL', variableManageSort: 'highest_value', - variableManagePage: 1 + variableManagePage: 1, + monitorSnapshot: createEmptyMonitorSnapshot() }; describe('useWiredCreatorToolsUiStore', () => @@ -43,6 +45,7 @@ describe('useWiredCreatorToolsUiStore', () => expect(state.variableManageTypeFilter).toBe('ALL'); expect(state.variableManageSort).toBe('highest_value'); expect(state.variableManagePage).toBe(1); + expect(state.monitorSnapshot).toEqual(createEmptyMonitorSnapshot()); }); describe('setIsVisible', () => @@ -177,4 +180,57 @@ describe('useWiredCreatorToolsUiStore', () => expect(useWiredCreatorToolsUiStore.getState().variableManagePage).toBe(2); }); }); + + describe('monitorSnapshot', () => + { + it('setMonitorSnapshot replaces the snapshot with the server payload shape', () => + { + const next = { + ...createEmptyMonitorSnapshot(), + usageCurrentWindow: 7, + usageLimitPerWindow: 10, + isHeavy: true, + averageExecutionMs: 42 + }; + + useWiredCreatorToolsUiStore.getState().setMonitorSnapshot(next); + + expect(useWiredCreatorToolsUiStore.getState().monitorSnapshot).toEqual(next); + expect(useWiredCreatorToolsUiStore.getState().monitorSnapshot.isHeavy).toBe(true); + }); + + it('resetMonitorSnapshot returns a fresh empty snapshot (new reference)', () => + { + const populated = { + ...createEmptyMonitorSnapshot(), + usageCurrentWindow: 5, + logs: [ { amount: 1, latestOccurrenceSeconds: 0, latestReason: '', latestSourceId: 0, latestSourceLabel: '', severity: 'ERROR', type: 'foo' } ], + history: [ { occurredAtSeconds: 0, reason: '', sourceId: 0, sourceLabel: '', severity: 'ERROR', type: 'foo' } ] + }; + useWiredCreatorToolsUiStore.getState().setMonitorSnapshot(populated); + + useWiredCreatorToolsUiStore.getState().resetMonitorSnapshot(); + + const cleared = useWiredCreatorToolsUiStore.getState().monitorSnapshot; + expect(cleared).toEqual(createEmptyMonitorSnapshot()); + expect(cleared).not.toBe(populated); + expect(cleared.logs).toEqual([]); + expect(cleared.history).toEqual([]); + }); + + it('the snapshot persists across the panel close/reopen lifecycle (UI flag flip)', () => + { + // Server pushed a non-empty snapshot while the panel was open. + const payload = { ...createEmptyMonitorSnapshot(), usageCurrentWindow: 3 }; + useWiredCreatorToolsUiStore.getState().setMonitorSnapshot(payload); + + // User closes the panel — UI flag flips, snapshot should NOT reset. + useWiredCreatorToolsUiStore.getState().setIsVisible(false); + + // User reopens — the last-known stats are still there. + useWiredCreatorToolsUiStore.getState().setIsVisible(true); + + expect(useWiredCreatorToolsUiStore.getState().monitorSnapshot.usageCurrentWindow).toBe(3); + }); + }); }); diff --git a/src/components/wired-tools/wiredCreatorToolsUiStore.ts b/src/components/wired-tools/wiredCreatorToolsUiStore.ts index 34aaf68..f4e754c 100644 --- a/src/components/wired-tools/wiredCreatorToolsUiStore.ts +++ b/src/components/wired-tools/wiredCreatorToolsUiStore.ts @@ -1,5 +1,6 @@ import { createNitroStore } from '../../state/createNitroStore'; -import { InspectionElementType, VariablesElementType, WiredToolsTab } from './WiredCreatorTools.types'; +import { createEmptyMonitorSnapshot } from './WiredCreatorTools.helpers'; +import { InspectionElementType, MonitorSnapshot, VariablesElementType, WiredToolsTab } from './WiredCreatorTools.types'; type MonitorSeverityFilter = 'ALL' | 'ERROR' | 'WARNING'; type Updater = T | ((prev: T) => T); @@ -27,6 +28,15 @@ interface WiredCreatorToolsUiState variableManageSort: string; variableManagePage: number; + /** + * Latest snapshot pushed by the server through `WiredMonitorDataEvent`. + * Held in the store (rather than `useState`) so it survives remount + * — e.g. closing and reopening the panel between two server pushes + * keeps the last-known stats visible instead of flashing back to the + * empty snapshot. + */ + monitorSnapshot: MonitorSnapshot; + setIsVisible: (next: Updater) => void; setActiveTab: (next: WiredToolsTab) => void; setInspectionType: (next: InspectionElementType) => void; @@ -44,6 +54,9 @@ interface WiredCreatorToolsUiState setVariableManageTypeFilter: (next: string) => void; setVariableManageSort: (next: string) => void; setVariableManagePage: (next: Updater) => void; + + setMonitorSnapshot: (next: MonitorSnapshot) => void; + resetMonitorSnapshot: () => void; } export const useWiredCreatorToolsUiStore = createNitroStore()((set) => ({ @@ -65,6 +78,8 @@ export const useWiredCreatorToolsUiStore = createNitroStore set(state => ({ isVisible: apply(state.isVisible, next) })), setActiveTab: (next) => set({ activeTab: next }), setInspectionType: (next) => set({ inspectionType: next }), @@ -81,5 +96,8 @@ export const useWiredCreatorToolsUiStore = createNitroStore set({ variableManageTypeFilter: next }), setVariableManageSort: (next) => set({ variableManageSort: next }), - setVariableManagePage: (next) => set(state => ({ variableManagePage: apply(state.variableManagePage, next) })) + setVariableManagePage: (next) => set(state => ({ variableManagePage: apply(state.variableManagePage, next) })), + + setMonitorSnapshot: (next) => set({ monitorSnapshot: next }), + resetMonitorSnapshot: () => set({ monitorSnapshot: createEmptyMonitorSnapshot() }) }));