diff --git a/src/renderer/src/components/NativeChatResumeOnRestartModal.tsx b/src/renderer/src/components/NativeChatResumeOnRestartModal.tsx index 38a2fe622a9..2d243cdc5da 100644 --- a/src/renderer/src/components/NativeChatResumeOnRestartModal.tsx +++ b/src/renderer/src/components/NativeChatResumeOnRestartModal.tsx @@ -15,6 +15,7 @@ import { useAppStore } from '../store' import { callStructuredAgentSession } from '@/runtime/structured-agent-session-client' import { translate } from '@/i18n/i18n' import { formatUiRelativeTime } from '@/i18n/relative-time-format' +import { parseWorkspaceKey } from '../../../shared/workspace-scope' /** * What would be reconnected, shown before anything runs. @@ -66,6 +67,69 @@ function announceResumed(count: number): void { ) } +/** + * A workspace id is not a name. `folder:` identifies nothing at twenty rows, and recognising + * which chats would reconnect is the entire point of this list. + * + * Resolved the way automation dispatch already resolves the same id space: a folder workspace is + * keyed by its full `folder:` key, a git worktree by its bare `repoId::path` id. Falls back + * to the id — what the row showed before — when nothing is resolvable, which also covers the window + * before the worktree store has hydrated. + */ +function useWorkspaceLabel(workspaceId: string): string { + // Returns a primitive, so the selector re-runs freely without churning referential equality. + return useAppStore((store) => { + const scope = parseWorkspaceKey(workspaceId) + const worktree = + scope?.type === 'folder' + ? store.getKnownWorktreeById(workspaceId) + : store.allWorktrees().find((entry) => entry.id === workspaceId) + return worktree?.displayName ?? workspaceId + }) +} + +/** Its own component because a hook cannot run inside `map`, and the label needs one per row. */ +function ResumeOnRestartRow({ + candidate, + listedAt, + busy, + onReconnect +}: { + candidate: ResumeCandidate + listedAt: number + busy: boolean + onReconnect: () => void +}): React.JSX.Element { + const workspaceLabel = useWorkspaceLabel(candidate.workspaceId) + return ( +
  • +
    +

    + {candidate.latestPrompt.trim() || + translate('auto.components.NativeChatResumeOnRestartModal.untitled', 'Untitled chat')} +

    + {/* Age matters: the TTL is 24h, so an eight-hour-old offer must not look like one from a + minute ago. */} +

    + {`${candidate.agent} · ${workspaceLabel} · ${formatUiRelativeTime( + candidate.recordedAt - listedAt + )}`} +

    +
    + +
  • + ) +} + export function NativeChatResumeOnRestartModal(): React.JSX.Element | null { const structuredEnabled = useAppStore( (store) => store.settings?.experimentalStructuredNativeChat === true @@ -207,34 +271,13 @@ export function NativeChatResumeOnRestartModal(): React.JSX.Element | null { className="flex min-h-0 flex-col gap-1 overflow-y-auto scrollbar-sleek rounded-md border bg-muted/35 p-1.5" > {candidates.map((candidate) => ( -
  • -
    -

    - {candidate.latestPrompt.trim() || - translate( - 'auto.components.NativeChatResumeOnRestartModal.untitled', - 'Untitled chat' - )} -

    - {/* Age matters: the TTL is 24h, so an eight-hour-old offer must not look like one - from a minute ago. */} -

    - {`${candidate.agent} · ${candidate.workspaceId} · ${formatUiRelativeTime( - candidate.recordedAt - listedAt - )}`} -

    -
    - -
  • + void resume([candidate.sessionId])} + /> ))}