diff --git a/app/renderer/sections/Sessions.test.tsx b/app/renderer/sections/Sessions.test.tsx
index 8b38ed0..710daf0 100644
--- a/app/renderer/sections/Sessions.test.tsx
+++ b/app/renderer/sections/Sessions.test.tsx
@@ -299,4 +299,36 @@ describe('Sessions', () => {
expect(within(filter).getByRole('button', { name: 'Codex' })).toHaveAttribute('aria-pressed', 'true')
expect(within(filter).getByRole('button', { name: 'All' })).toHaveAttribute('aria-pressed', 'false')
})
+
+ it('headlines a captured title, demotes the id, and falls back to the project when untitled', async () => {
+ getSessions.mockResolvedValue([
+ session({ sessionId: 'claude-abc123456789xyz', project: 'codeburn', provider: 'claude', title: 'Fix lifetime period in menubar labels', cost: 5 }),
+ session({ sessionId: 'claude-untitled-000000', project: 'docs-site', provider: 'claude', title: '', cost: 3 }),
+ ])
+ const { container } = render(
)
+ await screen.findByText('2 sessions · $8.00 · 2K tokens')
+
+ const titles = [...container.querySelectorAll('.session-row .session-title')]
+ const ids = [...container.querySelectorAll('.session-row .session-project')]
+ // Titled row (higher cost, first): the title is the headline, the id its mono secondary line.
+ expect(titles[0]).toHaveTextContent('Fix lifetime period in menubar labels')
+ expect(ids[0]).toHaveTextContent('claude-abc')
+ // Untitled row: unchanged behavior, the project (via shortenProjectPath) stays the headline.
+ expect(titles[1]).toHaveTextContent('docs/site')
+ expect(ids[1]).toHaveTextContent('claude-untitled')
+ })
+
+ it('matches sessions by their captured title', async () => {
+ const user = userEvent.setup()
+ getSessions.mockResolvedValue([
+ session({ sessionId: 'claude-1', project: 'codeburn', provider: 'claude', title: 'Refactor the parser cache', cost: 5 }),
+ session({ sessionId: 'codex-2', project: 'client-api', provider: 'codex', title: 'Add billing webhook', cost: 3 }),
+ ])
+ const { container } = render(
)
+ const search = await screen.findByRole('textbox', { name: 'Search sessions' })
+
+ await user.type(search, 'webhook')
+ expect(container.querySelectorAll('.session-row')).toHaveLength(1)
+ expect(container.querySelector('.session-row .session-title')).toHaveTextContent('Add billing webhook')
+ })
})
diff --git a/app/renderer/sections/Sessions.tsx b/app/renderer/sections/Sessions.tsx
index 4baea6e..3d0297e 100644
--- a/app/renderer/sections/Sessions.tsx
+++ b/app/renderer/sections/Sessions.tsx
@@ -125,6 +125,7 @@ export function Sessions({
const rows = report.data ?? []
const q = query.trim().toLowerCase()
const filtered = rows.filter(row => q === '' || [
+ row.title ?? '',
row.project,
row.sessionId,
row.models.join(' '),
@@ -251,7 +252,7 @@ export function Sessions({
›
- {shortenProjectPath(entry.row.project)}
+ {entry.row.title || shortenProjectPath(entry.row.project)}
{entry.row.sessionId.slice(0, 18)}
diff --git a/app/renderer/styles/plain.css b/app/renderer/styles/plain.css
index f666e05..27af73f 100644
--- a/app/renderer/styles/plain.css
+++ b/app/renderer/styles/plain.css
@@ -622,6 +622,9 @@ td:first-child { font-size: var(--fs-body); font-weight: var(--fw-body); }
.ov-outcome-metrics span { color: var(--mut); font-size: 10px; line-height: 1.25; }
.ov-outcome-metrics strong { overflow: hidden; color: var(--ink); font-family: var(--mono); font-size: var(--fs-kpi); font-weight: var(--fw-kpi); font-variant-numeric: tabular-nums; text-overflow: ellipsis; white-space: nowrap; }
.ov-outcome-split { margin-top: 9px; color: var(--mut); font-size: 10.5px; font-variant-numeric: tabular-nums; line-height: 1.4; }
+.ov-priced-chip { flex: 0 0 auto; margin-left: auto; display: inline-flex; align-items: center; border-radius: 999px; padding: 2px 8px; background: color-mix(in srgb, var(--warn) 14%, transparent); color: var(--warn); font-size: 10px; font-weight: 650; font-variant-numeric: tabular-nums; line-height: 1.4; }
+.ov-workflow-rework { margin-top: 10px; color: var(--mut); font-size: 11.5px; font-variant-numeric: tabular-nums; line-height: 1.4; }
+.ov-workflow-rework strong { color: var(--ink); font-family: var(--mono); font-weight: 600; }
.ov-routing { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 11px 13px; }
.ov-routing p { margin: 4px 0 0; color: var(--mut); font-size: 11.5px; line-height: 1.4; }
.ov-routing p strong { color: var(--ink); font-weight: 620; }