From be234548faa176ab38715f506631f90dc6b4c21b Mon Sep 17 00:00:00 2001 From: Tom Rochette Date: Fri, 21 Aug 2026 09:51:06 -0400 Subject: [PATCH] feat(ui): show externally-created sessions in sidebar and Recent view (#2930) * feat(ui): show externally-created sessions in sidebar and Recent view (#1441) Sessions created by external OpenCode processes (CLI, other server instances) share the same database but don't broadcast SSE events to OpenChamber's connected server. The existing 45s global polling via experimental.session.list() already discovers these sessions, but the isKnownActiveSessionDirectory filter in SessionSidebar.tsx removes them when their directory isn't a registered project. Add effectiveKnownDirectories that extends knownSessionDirectories with directories derived from globalActiveSessions themselves, so external sessions pass the filter and appear in the sidebar and Recent view. Validated with two live OpenCode server processes sharing the same database: global experimental.session.list() returns external sessions immediately, while per-directory APIs do not. * Revert "feat(ui): show externally-created sessions in sidebar and Recent view (#1441)" This reverts commit 94acda43ad3049e7674d8eb3f24b1f74acb0d1c3. * fix(ui): make sidebar reactive to global session store updates (#1441) Replace non-reactive useGlobalSessionsStore.getState() with direct reactive subscriptions for activeSessions and archivedSessions. The previous getState() pattern relied on proxy subscriptions (activeSessionStructure, hasAuthoritativeGlobalSessions) to trigger re-renders, which created a fragility gap where store updates could go unnoticed by the sidebar. Add a 45s periodic refreshGlobalSessions() interval in the sidebar, independent of the tray sync hook. The tray sync's polling is gated behind isTrayPlatform() and isTrayEnabled(), so on runtimes where the tray is unavailable, there was no periodic refresh of global sessions at all. External sessions (created by CLI or other server processes) were only discovered on initial mount or page refresh. --- .../ui/src/components/session/SessionSidebar.tsx | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/components/session/SessionSidebar.tsx b/packages/ui/src/components/session/SessionSidebar.tsx index fcb8461f..078ef2e4 100644 --- a/packages/ui/src/components/session/SessionSidebar.tsx +++ b/packages/ui/src/components/session/SessionSidebar.tsx @@ -450,9 +450,8 @@ const SessionSidebarComponent: React.FC = ({ const archivedSessionStructure = useGlobalSessionsStore(useShallow( (state) => state.archivedSessions.map(getSessionStructuralSignature).sort(), )); - const globalSessionSnapshot = useGlobalSessionsStore.getState(); - const globalActiveSessions = globalSessionSnapshot.activeSessions; - const archivedSessions = globalSessionSnapshot.archivedSessions; + const globalActiveSessions = useGlobalSessionsStore((state) => state.activeSessions); + const archivedSessions = useGlobalSessionsStore((state) => state.archivedSessions); const liveFallbackCacheRef = React.useRef<{ signature: string; sessions: Session[] }>({ signature: '', sessions: [], @@ -553,6 +552,13 @@ const SessionSidebarComponent: React.FC = ({ void refreshGlobalSessions(syncSessionsSnapshotRef.current); }, []); + React.useEffect(() => { + const interval = window.setInterval(() => { + void refreshGlobalSessions(); + }, 45_000); + return () => window.clearInterval(interval); + }, []); + React.useEffect(() => { let cancelled = false;