refactor(dashboard): head the card with the session name, move the worktree beside the project

The conversation name now sits next to the agent icon as the card's heading
rather than prefixing the user-message line, and the worktree drops to the
footer beside the project icon. The message line reads "You" again — the name
moved up, so keeping it there said the same thing twice.

Cards without a resolvable session name keep the worktree as the heading, and
the footer omits it rather than repeating it.
This commit is contained in:
Brennan Benson
2026-07-27 16:34:12 -07:00
parent f3473f2b79
commit 885f803adb
2 changed files with 33 additions and 20 deletions
@@ -127,19 +127,32 @@ describe('AgentKanbanCard', () => {
expect(idleClassName).not.toContain('amber')
})
it('labels the user message with the conversation name, falling back to "You"', () => {
renderCard({
it('heads the card with the conversation name and drops the worktree to the footer', () => {
const { container } = renderCard({
card: card({ lastUserMessage: 'ship it', conversationName: 'Sparse-checkout parser' }),
now: 2_000
})
expect(screen.getByText('Sparse-checkout parser')).toBeInTheDocument()
expect(screen.queryByText('You')).not.toBeInTheDocument()
cleanup()
renderCard({ card: card({ lastUserMessage: 'ship it' }), now: 2_000 })
const [header, footer] = [
container.querySelector('button')!.firstElementChild!,
container.querySelector('button')!.lastElementChild!
]
expect(header).toHaveTextContent('Sparse-checkout parser')
expect(header).not.toHaveTextContent('dashboard-review')
expect(footer).toHaveTextContent('dashboard-review')
// The message line is attributed to the user again — the name moved up.
expect(screen.getByText('You')).toBeInTheDocument()
})
it('heads the card with the worktree when no name resolves, without repeating it', () => {
const { container } = renderCard({ card: card({ lastUserMessage: 'ship it' }), now: 2_000 })
expect(screen.getAllByText('dashboard-review')).toHaveLength(1)
expect(container.querySelector('button')!.firstElementChild).toHaveTextContent(
'dashboard-review'
)
})
it('shows the repo as an icon labelled with its name instead of inline text', () => {
renderCard({
card: card(),
@@ -105,6 +105,11 @@ export const AgentKanbanCard = memo(
// neutral so the tint keeps meaning something.
const needsYou = card.bucket === 'attention'
const isDone = card.dotState === 'done'
// Why: the session's own name heads the card. Without one the worktree is
// the best heading left — and then the footer drops it rather than say it
// twice.
const heading = card.conversationName ?? card.worktreeName
const worktreeInFooter = card.conversationName !== undefined
return (
<button
@@ -135,7 +140,7 @@ export const AgentKanbanCard = memo(
card.unseen ? 'font-semibold text-foreground' : 'font-normal text-muted-foreground'
)}
>
{card.worktreeName}
{heading}
</span>
{/* The summary pill already carries the attention glyph. */}
{card.askSummary ? null : <AgentStateDot state={card.dotState} className="ml-auto" />}
@@ -145,15 +150,9 @@ export const AgentKanbanCard = memo(
<div className="flex flex-col gap-0.5">
{card.lastUserMessage ? (
<div className="line-clamp-1 text-[11px] leading-snug text-muted-foreground">
{/* Why: the chat's own name identifies the conversation far
better than "You" does; fall back when none resolves. */}
<span
className={cn(
'font-medium',
card.conversationName ? 'text-foreground/70' : 'text-foreground/45'
)}
>
{card.conversationName ?? translate('dashboardPopout.card.you', 'You')}
{/* Why: plain "You" again — the session's name now heads the card. */}
<span className="font-medium text-foreground/45">
{translate('dashboardPopout.card.you', 'You')}
</span>{' '}
{card.lastUserMessage}
</div>
@@ -180,9 +179,9 @@ export const AgentKanbanCard = memo(
</div>
) : null}
<div className="flex items-center gap-2 text-[11px] text-muted-foreground">
{/* Why: the repo's own icon never truncates and never competes with
the worktree name; the name itself lives in the tooltip. */}
<div className="flex items-center gap-1.5 text-[11px] text-muted-foreground">
{/* Why: the project reads as an icon so its name can't crowd the
worktree sitting next to it; the name lives in the tooltip. */}
<Tooltip>
<TooltipTrigger asChild>
<span
@@ -196,8 +195,9 @@ export const AgentKanbanCard = memo(
{card.repoName}
</TooltipContent>
</Tooltip>
{worktreeInFooter ? <span className="truncate">{card.worktreeName}</span> : null}
{displayTimestamp(card) > 0 ? (
<span className="ml-auto shrink-0 tabular-nums">
<span className="ml-auto shrink-0 pl-1 tabular-nums">
{formatStartedAgo(displayTimestamp(card), now)}
</span>
) : null}