diff --git a/apps/desktop/src/app/chat/sidebar/chrome.tsx b/apps/desktop/src/app/chat/sidebar/chrome.tsx index ef83cc6d94..5688736004 100644 --- a/apps/desktop/src/app/chat/sidebar/chrome.tsx +++ b/apps/desktop/src/app/chat/sidebar/chrome.tsx @@ -39,7 +39,12 @@ const rowPadTrail = 'pr-2' const rowGap = 'gap-1.5' const rowLead = 'grid size-3.5 shrink-0 place-items-center' const rowInset = cn(rowPadX, rowGap, 'flex h-full min-w-0 items-center self-stretch py-0.5') -const rowLabel = 'min-w-0 truncate text-[0.8125rem] leading-none text-(--ui-text-secondary)' +// `truncate` is overflow:hidden. `leading-none` (line-height: 1) makes the +// line box equal the em-square, so glyph ink that sticks out — Segoe UI on +// Windows is ~1.33em — gets shaved. 1.35 leaves room; the shell still owns +// row height, so the extra leading just centers. +export const SIDEBAR_TRUNCATED_LEADING = 'leading-[1.35]' as const +const rowLabel = cn('min-w-0 truncate text-[0.8125rem] text-(--ui-text-secondary)', SIDEBAR_TRUNCATED_LEADING) /** Inbox-style card (workspace + age, title + preview, model + size). */ export const SIDEBAR_ROW_CARD_MIN_H = 'min-h-[3.375rem]' as const diff --git a/apps/desktop/src/app/chat/sidebar/session-row.test.tsx b/apps/desktop/src/app/chat/sidebar/session-row.test.tsx index 38beaa3c4b..bfbb0f77c9 100644 --- a/apps/desktop/src/app/chat/sidebar/session-row.test.tsx +++ b/apps/desktop/src/app/chat/sidebar/session-row.test.tsx @@ -21,6 +21,11 @@ vi.mock('@/i18n', () => ({ useI18n: () => ({ t: { sidebar: { + messageCount: (count: number) => `${count} messages`, + toolCallCount: (count: number) => `${count} tool calls`, + projects: { + home: 'Home' + }, row: { ageMin: 'm', ageNow: 'now', @@ -31,6 +36,7 @@ vi.mock('@/i18n', () => ({ needsInput: 'Needs input', sessionActions: 'Session actions', sessionRunning: 'Running', + todoProgress: 'Tasks completed', waitingForAnswer: 'Waiting for answer' } }, @@ -150,9 +156,10 @@ const handoffAvatar = (container: HTMLElement) => const noop = vi.fn() -const renderRow = (session: SessionInfo) => +const renderRow = (session: SessionInfo, extra?: { card?: boolean }) => render( { expect(tipTrigger(avatar as HTMLElement)).toBeTruthy() }) }) + +describe('Inbox-style session card', () => { + it('gives truncated card lines room for glyph ink instead of clipping them', () => { + renderRow( + makeSession({ + cwd: '/Users/tomek/pursuit-support-agent', + message_count: 133, + model: 'gpt-4.1', + title: 'Ruff lint and pytest verification' + }), + { card: true } + ) + + const workspace = screen.getByText('pursuit-support-agent') + const title = screen.getByText('Ruff lint and pytest verification').parentElement + const footer = screen.getByText('GPT-4.1').parentElement + + expect(title).toBeTruthy() + expect(footer).toBeTruthy() + + for (const el of [workspace, title!, footer!]) { + expect(el.className).not.toMatch(/\bleading-none\b/) + expect(el.className).toMatch(/leading-\[1\.35\]/) + } + + expect(workspace.className).toMatch(/\btruncate\b/) + expect(screen.getByText('133 messages')).toBeTruthy() + }) +}) diff --git a/apps/desktop/src/app/chat/sidebar/session-row.tsx b/apps/desktop/src/app/chat/sidebar/session-row.tsx index aa88248daa..108319f3e0 100644 --- a/apps/desktop/src/app/chat/sidebar/session-row.tsx +++ b/apps/desktop/src/app/chat/sidebar/session-row.tsx @@ -38,6 +38,7 @@ import { SessionStatusDot } from '../session-status-dot' import { SIDEBAR_ROW_CARD_MIN_H, + SIDEBAR_TRUNCATED_LEADING, SidebarRowBody, SidebarRowGrab, SidebarRowLabel, @@ -416,7 +417,7 @@ function SidebarSessionRowImpl({ // The card is a grid with ONE spacing knob: --card-gap. Every row // gap is gap-y-(--card-gap); the title/preview group opts out // with its own tighter internal flex gap. - card && 'flex-col items-stretch justify-center py-1.5 [--card-gap:0.6rem] gap-(--card-gap)' + card && 'flex-col items-stretch justify-center py-1.5 [--card-gap:0.4rem] gap-(--card-gap)' )} // Middle-click = open in a new tab (browser muscle memory). {...middleClickHandlers(() => { @@ -505,12 +506,22 @@ function SidebarSessionRowImpl({ deterministic metadata line; detailed adds the initial request preview. Compact keeps today's one-line row. */} {density !== 'compact' && details.metadata && ( - + {details.metadata} )} {density === 'detailed' && details.preview && ( - + {details.preview} )} @@ -528,7 +539,12 @@ function SidebarSessionRowImpl({ entire width — nothing truncates against the kebab. */}
{leadNode} - + {context} {handoffBadge} @@ -536,10 +552,13 @@ function SidebarSessionRowImpl({
{/* Title + preview: ONE grouped cell with its own tight internal gap — it does not inherit the card's rhythm. */} -
+
@@ -547,13 +566,23 @@ function SidebarSessionRowImpl({ {session.preview && rowMeta.includes('preview') ? ( - + {session.preview} ) : null}
{model || size || todoProgress ? ( - + {model ? {model} : null} {size ? {size} : null} {todoProgress ? ( diff --git a/apps/desktop/src/app/chat/sidebar/virtual-session-list.tsx b/apps/desktop/src/app/chat/sidebar/virtual-session-list.tsx index e0ad175cb7..03843400cf 100644 --- a/apps/desktop/src/app/chat/sidebar/virtual-session-list.tsx +++ b/apps/desktop/src/app/chat/sidebar/virtual-session-list.tsx @@ -55,7 +55,7 @@ export interface VirtualSessionListProps { // Matches the card's typical rendered height (four lines when a preview // exists) so long card lists don't jump under the scroll thumb before // self-measurement catches up. -const CARD_ROW_ESTIMATE_PX = 66 +const CARD_ROW_ESTIMATE_PX = 74 const DIVIDER_ESTIMATE_PX = 28 const OVERSCAN_ROWS = 12