From 3f6cdd36f3d45fd766a9dfa9cd88c79749a401fd Mon Sep 17 00:00:00 2001 From: Shantur Date: Tue, 31 Mar 2026 18:38:54 +0100 Subject: [PATCH] feat(ui): surface retrying session status Preserve retry metadata from session.status events so the session list and header can show a live retry countdown with context. Notify users when a session enters retry and reuse the existing error styling so retrying feels actionable without losing the current badge layout. --- .../components/instance/instance-shell2.tsx | 28 ++++++-- packages/ui/src/components/session-list.tsx | 27 ++++++- .../ui/src/lib/i18n/messages/en/session.ts | 4 ++ .../ui/src/lib/i18n/messages/es/session.ts | 4 ++ .../ui/src/lib/i18n/messages/fr/session.ts | 4 ++ .../ui/src/lib/i18n/messages/he/session.ts | 4 ++ .../ui/src/lib/i18n/messages/ja/session.ts | 4 ++ .../ui/src/lib/i18n/messages/ru/session.ts | 4 ++ .../src/lib/i18n/messages/zh-Hans/session.ts | 4 ++ packages/ui/src/stores/session-api.ts | 6 +- packages/ui/src/stores/session-events.ts | 72 ++++++++++++++++--- packages/ui/src/stores/session-state.ts | 3 + packages/ui/src/stores/session-status.ts | 11 ++- packages/ui/src/styles/panels.css | 10 +++ .../ui/src/styles/panels/session-layout.css | 10 +++ packages/ui/src/types/session.ts | 19 +++++ 16 files changed, 194 insertions(+), 20 deletions(-) diff --git a/packages/ui/src/components/instance/instance-shell2.tsx b/packages/ui/src/components/instance/instance-shell2.tsx index bf10cc54..60fc717a 100644 --- a/packages/ui/src/components/instance/instance-shell2.tsx +++ b/packages/ui/src/components/instance/instance-shell2.tsx @@ -41,7 +41,7 @@ import SessionSidebar from "./shell/SessionSidebar" import { useSessionSidebarRequests } from "./shell/useSessionSidebarRequests" import RightPanel from "./shell/right-panel/RightPanel" import { useDrawerChrome } from "./shell/useDrawerChrome" -import { getSessionStatus } from "../../stores/session-status" +import { getRetrySeconds, getSessionRetry, getSessionStatus } from "../../stores/session-status" import { Maximize2, ShieldAlert } from "lucide-solid" import type { LayoutMode } from "./shell/types" @@ -97,6 +97,7 @@ const InstanceShell2: Component = (props) => { const [selectedBackgroundProcess, setSelectedBackgroundProcess] = createSignal(null) const [showBackgroundOutput, setShowBackgroundOutput] = createSignal(false) const [permissionModalOpen, setPermissionModalOpen] = createSignal(false) + const [now, setNow] = createSignal(Date.now()) // Worktree selector manages its own dialogs. const [showSessionSearch, setShowSessionSearch] = createSignal(false) @@ -230,6 +231,12 @@ const InstanceShell2: Component = (props) => { window.localStorage.setItem(RIGHT_DRAWER_STORAGE_KEY, rightDrawerWidth().toString()) }) + createEffect(() => { + if (typeof window === "undefined") return + const timer = window.setInterval(() => setNow(Date.now()), 1000) + onCleanup(() => window.clearInterval(timer)) + }) + const connectionStatus = () => sseManager.getStatus(props.instance.id) const connectionStatusClass = () => { const status = connectionStatus() @@ -272,17 +279,28 @@ const InstanceShell2: Component = (props) => { } const status = getSessionStatus(props.instance.id, activeSessionId) - const text = - status === "working" + const retry = getSessionRetry(props.instance.id, activeSessionId) + const text = retry + ? (() => { + const seconds = getRetrySeconds(retry.next, now()) + return seconds > 0 ? t("sessionList.status.retryingIn", { seconds: String(seconds) }) : t("sessionList.status.retrying") + })() + : status === "working" ? t("sessionList.status.working") : status === "compacting" ? t("sessionList.status.compacting") : t("sessionList.status.idle") return { - className: `session-${status}`, + className: `session-${retry ? "retrying" : status}`, text, showAlertIcon: false, + title: retry + ? t("sessionList.status.retryTooltip", { + message: retry.message, + attempt: String(retry.attempt), + }) + : undefined, } }) @@ -290,7 +308,7 @@ const InstanceShell2: Component = (props) => { const pill = activeSessionStatusPill() if (!pill) return null return ( - + {pill.showAlertIcon ? - + {needsInput() ?