Two module-level `let` declarations (cachedServerCommands +
globalListenerRegistered) were tracking the AvailableCommandsEvent
listener state outside React. The pattern was a React Compiler
violation flagged elsewhere in the codebase (the navigatorRoomCreator
fix was the canonical precedent — see commit fd1835c).
Move both into a per-hook Zustand store
(`useChatCommandStore`) following the same convention as
`useWiredCreatorToolsUiStore` and `useRoomCreatorStore`. The store
keeps the cached server-pushed CommandDefinitions plus a
single-shot isListenerRegistered flag that prevents the in-hook
useMessageEvent and the module-level pre-mount listener from
double-registering.
`CLIENT_COMMANDS` stays at module scope — it's a const array,
React Compiler is fine with constant data.
Behavioural change: zero. The pre-mount registration still tries
once at module load (covering the case where the server's
AvailableCommands lands before any React widget mounts). The in-hook
useMessageEvent still covers later mounts and rank-change refreshes.
Every push goes through `setServerCommands`, so all consumers see
the same data.
Side benefit: a future test can now `useChatCommandStore.setState({
serverCommands: [...], isListenerRegistered: true })` to seed a
deterministic fixture without monkey-patching the module.
Public API of useChatCommandSelector unchanged; the one consumer
(ChatInputView) reads the same destructured fields. Verified via grep.
Suite: 207/207.
Server-authoritative command list via packet 4050, merged with
client-only commands. Supports keyboard navigation, filtering,
and module-level caching to handle login-time packet timing.
Co-Authored-By: medievalshell <medievalshell@users.noreply.github.com>