Plan/1737 connection status indicator#4
Conversation
…penchamber#1556) - Hop-separated narrow state in useConfigStore (runtimeTransport, openCodeRuntime) driven by the existing SSE/WS reconnect pipeline and the existing opencodeClient.checkHealth() path. No new polling loop. - Pure helper packages/ui/src/lib/connection-status/connectionStatus.ts that maps per-hop internal state + navigator.onLine to a single aggregated view model (connected / reconnecting / degraded / disconnected / unknown) and short i18n keys. Raw internal reason codes are kept in the view model for diagnostics only and never reach the UI. - Compact header indicator packages/ui/src/components/layout/ ConnectionStatusIndicator.tsx (one dot + hover tooltip with 2-3 short lines, theme tokens only, React.memo on both layers, narrow leaf selectors). Wired into Header.tsx in the existing desktopSidebarActions cluster. - 19 new connectionStatus.* i18n keys in all 9 dictionaries. - 22 unit tests for the mapper covering all Phase 5 scenarios. - One-line pre-existing fr.ts parity fix (sessions.scheduledTasks.editor.scheduleType.cron) so the i18n parity test passes; flagged in the PR body. Validation: type-check + lint green in packages/ui, packages/web, packages/electron. docs:validate green. 22 + 12 + 2 new/updated tests pass. 5 pre-existing sync-pipeline test failures verified unrelated to this diff.
There was a problem hiding this comment.
Code Review
This pull request introduces a compact, aggregated ConnectionStatusIndicator component in the header to track and display connection health across two hops: Frontend to OpenChamber runtime, and OpenChamber runtime to OpenCode. It adds the corresponding state management in useConfigStore, integrates browser-level online/offline listeners in sync-context.tsx, and provides localized messages and unit tests. The review feedback highlights a bug where navigatorOfflineRef is never updated after mount, suggesting reading navigator.onLine dynamically instead. Additionally, the reviewer recommends avoiding prop-drilling the translation function t by using the useI18n hook directly inside the memoized body component, and suggests mirroring the active connectionPhase in sync-context.tsx rather than hardcoding a "disconnected" phase to prevent overriding active reconnection states.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| const navigatorOfflineRef = React.useRef<boolean>( | ||
| typeof navigator === 'object' && navigator !== null && navigator.onLine === false, | ||
| ); | ||
| const navigatorOffline = navigatorOfflineRef.current; |
There was a problem hiding this comment.
The navigatorOfflineRef is initialized once on mount and its value is never updated. As a result, navigatorOffline remains constant throughout the component's lifecycle.
If the application is mounted while offline, navigatorOffline will be permanently stuck at true. Even after the network is restored and the transport successfully reconnects, the view model will continue to evaluate navigatorOffline as true and display the "Disconnected" status.
Since ConnectionStatusIndicator already re-renders whenever runtimeTransport or openCodeRuntime changes (which is guaranteed to happen on network changes due to the listeners in sync-context.tsx), we can safely read navigator.onLine dynamically during render instead of using a stale ref.
const navigatorOffline =
typeof navigator === 'object' && navigator !== null && navigator.onLine === false;
| type ConnectionStatusIndicatorBodyProps = { | ||
| viewModel: ConnectionStatusViewModel; | ||
| /** | ||
| * The i18n `t` function, passed down from the parent so the body can be | ||
| * memoized purely on `viewModel`. When the locale changes, the parent | ||
| * re-renders with a new `t` reference and the body picks it up. | ||
| */ | ||
| t: (key: I18nKey, params?: I18nParams) => string; | ||
| }; |
| const ConnectionStatusIndicatorBody = React.memo(function ConnectionStatusIndicatorBody({ | ||
| viewModel, | ||
| t, | ||
| }: ConnectionStatusIndicatorBodyProps) { |
There was a problem hiding this comment.
Instead of passing the t translation function as a prop from the parent, you can call useI18n() directly inside ConnectionStatusIndicatorBody.
Since ConnectionStatusIndicatorBody is wrapped in React.memo, removing the t prop ensures it is memoized purely on the viewModel reference. When the locale changes, the i18n context will automatically trigger a re-render of ConnectionStatusIndicatorBody anyway, keeping the translations up-to-date without prop-drilling.
| const ConnectionStatusIndicatorBody = React.memo(function ConnectionStatusIndicatorBody({ | |
| viewModel, | |
| t, | |
| }: ConnectionStatusIndicatorBodyProps) { | |
| const ConnectionStatusIndicatorBody = React.memo(function ConnectionStatusIndicatorBody({ | |
| viewModel, | |
| }: ConnectionStatusIndicatorBodyProps) { | |
| const { t } = useI18n(); |
| [runtimeTransport, openCodeRuntime, navigatorOffline], | ||
| ); | ||
|
|
||
| return <ConnectionStatusIndicatorBody viewModel={viewModel} t={t} />; |
There was a problem hiding this comment.
| useConfigStore.getState().setRuntimeTransportState({ | ||
| phase: "disconnected", | ||
| reason, | ||
| updatedAt: Date.now(), | ||
| }) |
There was a problem hiding this comment.
Currently, when the transport disconnects, runtimeTransportState is updated with phase: "disconnected". However, the event pipeline is actually in a "reconnecting" or "connecting" phase (as tracked by connectionPhase).
By hardcoding "disconnected", the connection status indicator will immediately show the red "Disconnected" dot instead of the neutral/muted "Reconnecting" dot during active reconnection attempts. This makes the "reconnecting" and "connecting" phases in the view model dead code.
We should mirror the active connectionPhase (which uses hasEverConnected ? "reconnecting" : "connecting") so the indicator can accurately reflect the transitional states.
useConfigStore.getState().setRuntimeTransportState({
phase: hasEverConnected ? "reconnecting" : "connecting",
reason,
updatedAt: Date.now(),
})
No description provided.