forked from molecule-ai/molecule-core
Both AgentCommsPanel and ChatTab's activity-feed opened raw
`new WebSocket(WS_URL)` instances per mount, with no onclose handler
and no reconnect logic. When the underlying connection dropped — idle
timeout, browser background-tab throttle, network jitter — the per-
panel sockets stayed dead until the panel re-mounted (refresh or
sub-tab unmount/remount). Live agent-comms bubbles and live activity
feed lines silently went missing in the gap, manifesting as "the
delegation didn't show up until I refreshed."
The global ReconnectingSocket in store/socket.ts already owns
reconnect, exponential backoff, health-check, and HTTP fallback poll.
Routing component subscribers through it gives every consumer those
guarantees for free, with one TCP connection per tab instead of N.
Three new pieces:
- store/socket-events.ts: tiny pub/sub bus. emitSocketEvent fan-outs
every decoded WSMessage to the listener Set; subscribeSocketEvents
returns an unsubscribe. A throwing listener is logged and isolated
so it can't break siblings.
- store/socket.ts: ws.onmessage now calls emitSocketEvent(msg) right
after applyEvent(msg), so the store's derived state and component
subscribers stay in lockstep on every event arrival.
- hooks/useSocketEvent.ts: React hook that registers exactly once
per mount, capturing the latest handler in a ref so the closure
sees current state/props without re-subscribing on every render.
Refactored sites:
- AgentCommsPanel: replaced its WebSocket-in-useEffect block with
useSocketEvent. Same parsing logic; the panel no longer opens its
own connection.
- ChatTab activity feed: split the previous useEffect in two — one
seeds the activity log when `sending` flips, the other subscribes
unconditionally and gates work on `sending` inside the handler.
Hooks can't be conditional, so the gate has to live in the body
rather than around the effect.
The ws-close graceful-close helper is no longer needed in either
site; the global socket owns its own teardown.
Tests: 6 new tests for the bus contract (single delivery, fan-out
order, unsubscribe, throwing-listener isolation, no-subscriber emit,
duplicate-subscribe Set semantics). All 27 existing socket tests
still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| __tests__ | ||
| canvas | ||
| settings | ||
| tabs | ||
| ui | ||
| A2ATopologyOverlay.tsx | ||
| ApprovalBanner.tsx | ||
| AuditTrailPanel.tsx | ||
| AuthGate.tsx | ||
| BatchActionBar.tsx | ||
| BundleDropZone.tsx | ||
| Canvas.tsx | ||
| CommunicationOverlay.tsx | ||
| ConfirmDialog.tsx | ||
| ConsoleModal.tsx | ||
| ContextMenu.tsx | ||
| ConversationTraceModal.tsx | ||
| CookieConsent.tsx | ||
| CreateWorkspaceDialog.tsx | ||
| DeleteCascadeConfirmDialog.tsx | ||
| EmptyState.tsx | ||
| ErrorBoundary.tsx | ||
| ExternalConnectModal.tsx | ||
| Legend.tsx | ||
| MemoryInspectorPanel.tsx | ||
| MissingKeysModal.tsx | ||
| OnboardingWizard.tsx | ||
| OrgImportPreflightModal.tsx | ||
| PricingTable.tsx | ||
| ProvisioningTimeout.tsx | ||
| SearchDialog.tsx | ||
| SidePanel.tsx | ||
| Spinner.tsx | ||
| StatusDot.tsx | ||
| TemplatePalette.tsx | ||
| TermsGate.tsx | ||
| Toaster.tsx | ||
| Toolbar.tsx | ||
| Tooltip.tsx | ||
| WorkspaceNode.tsx | ||
| WorkspaceUsage.tsx | ||