From af3d4a687fa998176bfadbb0cfb3cab210d97c9a Mon Sep 17 00:00:00 2001 From: Ben Date: Thu, 21 May 2026 18:01:34 +1000 Subject: [PATCH] fix(dashboard-auth): ChatPage cleanup closes WS via wsRef.current Phase 5.3 (1c99c2f5e) wrapped the WS construction in an IIFE so the gated-mode ticket fetch could resolve asynchronously, but the effect's top-level cleanup still referenced the IIFE-scoped `const ws`. TypeScript catches it at build time: src/pages/ChatPage.tsx:654:7 - error TS2304: Cannot find name 'ws'. LSP-cache-lag drowned the diagnostic under the JSX-types-missing noise locally, so the bug shipped uncaught. Switch to `wsRef.current?.close()` which: - resolves to the same WebSocket the IIFE assigned (line 562: `wsRef.current = ws`) - is null-safe when unmount races the ticket fetch (the IIFE early- returns on `unmounting` so wsRef.current is never set) The ChatSidebar.tsx + gatewayClient.ts cleanup paths were already using this pattern correctly (`ws?.close()` / `ws` was hoisted), so this fix is ChatPage-only. --- web/src/pages/ChatPage.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/web/src/pages/ChatPage.tsx b/web/src/pages/ChatPage.tsx index e5a6532eb8d..bbbe5a79ec7 100644 --- a/web/src/pages/ChatPage.tsx +++ b/web/src/pages/ChatPage.tsx @@ -651,7 +651,12 @@ export default function ChatPage({ isActive = true }: { isActive?: boolean }) { if (hostSyncRaf) cancelAnimationFrame(hostSyncRaf); if (settleRaf1) cancelAnimationFrame(settleRaf1); if (settleRaf2) cancelAnimationFrame(settleRaf2); - ws.close(); + // Phase 5.3: ``ws`` is local to the IIFE that opens it (the gated-mode + // ticket fetch makes the open async). The cleanup runs at the outer + // effect's top level so it can't reach into that scope — close via + // the ref instead. ``?.`` covers the race where unmount fires before + // the ticket fetch resolves and ``wsRef.current`` was never assigned. + wsRef.current?.close(); wsRef.current = null; term.dispose(); termRef.current = null;