What happened
Multiple surfaces let the user pick a past conversation (resume, prompt search, prompts list), and each one presents sessions differently. Some show a truncated provider session ID like `8d6926a5`, some show the cwd basename like `agent-code`, some show the first user prompt, some show some mixture depending on what's available. The result is that the same conversation shows up under different identities depending on where you're looking at it from, and there's no single way to ask "which past conversation am I looking at?".
`PromptSearchModal.tsx` itself acknowledges the friction inline:
// Session names are useless for recognizing conversations. This modal …
…but the rest of the surfaces don't share that conviction or the helpers around it. The display layer is fragmented across at least three modals, a shared helper module, two main-process IPC handlers, and two different transcript-listing primitives in the submodule packages.
Repro
- Open the command palette → "Search Conversation Prompts" — see one rendering shape (prompt-led).
- Open the resume picker (PathPickerModal → "Resume…" affordance, or whatever the current entrypoint is) — see a different rendering shape (cwd/id-led).
- Open "View Prompts" on a current session vs. "Rewind to Prompt" — different shapes again.
The exact disagreement varies by entry point and what fields are populated in the underlying transcript. The common failure mode: you remember a conversation by its content (the first prompt you sent, or the topic) but the picker is showing you a hex ID or a folder basename instead.
Where
The fragmentation surface:
Renderer modals (each renders the same conceptual thing differently):
- `src/renderer/src/features/workspace/ui/PromptSearchModal.tsx` — currently the most thought-through (acknowledges the "names are useless" framing, displays prompt + cwd basename + provider glyph)
- `src/renderer/src/features/workspace/ui/ViewPromptsModal.tsx` — list-prompts shape, different display
- `src/renderer/src/features/workspace/ui/RewindToPromptModal.tsx` — rewind-target picker, different display again
- PathPickerModal's resume affordance (`src/renderer/src/features/path-picker/ui/PathPickerModal.tsx`)
Shared display helpers (the right place to consolidate):
- `src/renderer/src/features/workspace/lib/sessionDisplay.ts` — exists, used inconsistently
- `src/renderer/src/features/workspace/lib/latestUserPrompts.ts` — feeds the rewind picker
Main-process IPC + indexing:
Provider-side primitives:
- `packages/claude-code-headless/src/transcript/SessionList.ts` — `readSessionLite` returns head+tail snippets used to derive a display
- `packages/codex-headless/src/transcript/SessionList.ts` — same shape for Codex
Output
N/A — UX-reliability issue, no crash.
State
Affects every surface that lets the user pick a past conversation. Worse on Codex sessions (rollout file names are timestamp-prefixed UUIDs) and on Claude sessions that don't have a meaningful first prompt (e.g. continuation sessions).
Sketch
This needs to be re-thought as a system, not patched per modal. Pieces:
-
One canonical "display identity" for a past session. A typed record like:
```ts
type SessionDisplayIdentity = {
// Required
providerSessionId: string // raw id, always present
kind: 'claude' | 'codex'
// Best-effort display fields, derived once at index time
firstUserPrompt: string | null // null on continuations / empty sessions
projectBasename: string | null // last segment of cwd
lastActivityAt: number // mtime
turnCount: number // user-prompt count
}
```
Built once in `sessionIndex.ts` (during the scan it already does — and which should become streaming per the open perf work). Served via a single IPC. No more `readSessionLite` head/tail re-derivation at the renderer.
-
One renderer component for displaying a session row, consumed by every modal. Probably `SessionPickerRow` under `src/renderer/src/features/workspace/ui/`, fed by the identity record above. Locks in: provider glyph + first-prompt preview + project basename + relative "last activity" + turn count. Long-form. No truncated hex IDs in the primary line.
-
One identity hierarchy when fields are missing. A continuation session with no first prompt should fall through to (most recent prompt) → (cwd basename) → (truncated id), in that order, never the other way around. The fallback should be visible (e.g. italicised) so the user knows they're looking at a fallback identity, not a real name.
-
Drop the resume picker's separate codepath. Resume entry points should consume the same `SessionPickerRow` shape so a session looks identical whether the user found it via search, resume, or rewind.
-
Migrate one modal at a time so the redesign doesn't merge as one giant unreviewable PR. Order: PromptSearchModal (already closest to the target shape) → resume picker → ViewPromptsModal → RewindToPromptModal.
The end state: there is one answer to "what does this past conversation look like in a picker?" and four modals that all answer the same way.
What happened
Multiple surfaces let the user pick a past conversation (resume, prompt search, prompts list), and each one presents sessions differently. Some show a truncated provider session ID like `8d6926a5`, some show the cwd basename like `agent-code`, some show the first user prompt, some show some mixture depending on what's available. The result is that the same conversation shows up under different identities depending on where you're looking at it from, and there's no single way to ask "which past conversation am I looking at?".
`PromptSearchModal.tsx` itself acknowledges the friction inline:
…but the rest of the surfaces don't share that conviction or the helpers around it. The display layer is fragmented across at least three modals, a shared helper module, two main-process IPC handlers, and two different transcript-listing primitives in the submodule packages.
Repro
The exact disagreement varies by entry point and what fields are populated in the underlying transcript. The common failure mode: you remember a conversation by its content (the first prompt you sent, or the topic) but the picker is showing you a hex ID or a folder basename instead.
Where
The fragmentation surface:
Renderer modals (each renders the same conceptual thing differently):
Shared display helpers (the right place to consolidate):
Main-process IPC + indexing:
Provider-side primitives:
Output
N/A — UX-reliability issue, no crash.
State
Affects every surface that lets the user pick a past conversation. Worse on Codex sessions (rollout file names are timestamp-prefixed UUIDs) and on Claude sessions that don't have a meaningful first prompt (e.g. continuation sessions).
Sketch
This needs to be re-thought as a system, not patched per modal. Pieces:
One canonical "display identity" for a past session. A typed record like:
```ts
type SessionDisplayIdentity = {
// Required
providerSessionId: string // raw id, always present
kind: 'claude' | 'codex'
// Best-effort display fields, derived once at index time
firstUserPrompt: string | null // null on continuations / empty sessions
projectBasename: string | null // last segment of cwd
lastActivityAt: number // mtime
turnCount: number // user-prompt count
}
```
Built once in `sessionIndex.ts` (during the scan it already does — and which should become streaming per the open perf work). Served via a single IPC. No more `readSessionLite` head/tail re-derivation at the renderer.
One renderer component for displaying a session row, consumed by every modal. Probably `SessionPickerRow` under `src/renderer/src/features/workspace/ui/`, fed by the identity record above. Locks in: provider glyph + first-prompt preview + project basename + relative "last activity" + turn count. Long-form. No truncated hex IDs in the primary line.
One identity hierarchy when fields are missing. A continuation session with no first prompt should fall through to (most recent prompt) → (cwd basename) → (truncated id), in that order, never the other way around. The fallback should be visible (e.g. italicised) so the user knows they're looking at a fallback identity, not a real name.
Drop the resume picker's separate codepath. Resume entry points should consume the same `SessionPickerRow` shape so a session looks identical whether the user found it via search, resume, or rewind.
Migrate one modal at a time so the redesign doesn't merge as one giant unreviewable PR. Order: PromptSearchModal (already closest to the target shape) → resume picker → ViewPromptsModal → RewindToPromptModal.
The end state: there is one answer to "what does this past conversation look like in a picker?" and four modals that all answer the same way.