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() }) }));