From d2dff405606704a9f8a6ed268b71aa15f90a661c Mon Sep 17 00:00:00 2001 From: imccyu <276526105+imccyu@users.noreply.github.com> Date: Fri, 31 Jul 2026 10:48:39 +0800 Subject: [PATCH] feat(ui-workspace): blank New Session rows render no menu and no time A blank row is a provisional placeholder: nothing has happened in it, so the row verbs (rename/fork/archive) and a 'now' stamp would act on content that does not exist. The trailing cells and the hover card's time line stay off until the first prompt lands. --- .../ui-workspace/src/client/rows/Rows.tsx | 62 +++++++++++-------- .../client/ui-workspace/tests/rows.spec.tsx | 23 +++++++ 2 files changed, 58 insertions(+), 27 deletions(-) diff --git a/packages/client/ui-workspace/src/client/rows/Rows.tsx b/packages/client/ui-workspace/src/client/rows/Rows.tsx index a8b28f17e4..77583140c1 100644 --- a/packages/client/ui-workspace/src/client/rows/Rows.tsx +++ b/packages/client/ui-workspace/src/client/rows/Rows.tsx @@ -175,7 +175,9 @@ function SessionHoverContent({ node, now, t }: { node: SessionNode; now: number; return (
{displayTitle(node, t)}
-
{hoverTimeLabel(node.updatedAt, now, t)}
+ {/* Same placeholder rule as the row's trailing cell: no timestamp + before the first prompt. */} + {!node.blank &&
{hoverTimeLabel(node.updatedAt, now, t)}
}
{node.running ? t('status.running') : t('status.idle')} @@ -306,32 +308,38 @@ export function SessionNodeItem({ node, currentId, now, onOpen, onRename, onFork > {row.running && } {title} - {timeLabel(row.updatedAt, now, t)} - - { setMenuOpen(false) }} - items={sessionMenuItems} - onSelect={(id) => { - setMenuOpen(false) - if (id === 'rename') onRename(node.id, row.title) - if (id === 'fork') onFork(node.id) - if (id === 'archive') onArchive(node.id) - }} - portal - closeOnPointerLeave - anchor={( - - )} - /> - + {/* A blank New Session row is a provisional placeholder: nothing has + happened in it yet, so a "now" timestamp and the row verbs + (rename/fork/archive) would all act on content that does not + exist — both trailing cells stay off until the first prompt. */} + {!row.blank && {timeLabel(row.updatedAt, now, t)}} + {!row.blank && ( + + { setMenuOpen(false) }} + items={sessionMenuItems} + onSelect={(id) => { + setMenuOpen(false) + if (id === 'rename') onRename(node.id, row.title) + if (id === 'fork') onFork(node.id) + if (id === 'archive') onArchive(node.id) + }} + portal + closeOnPointerLeave + anchor={( + + )} + /> + + )}
) return ( diff --git a/packages/client/ui-workspace/tests/rows.spec.tsx b/packages/client/ui-workspace/tests/rows.spec.tsx index ee45b8628d..c0b4959b17 100644 --- a/packages/client/ui-workspace/tests/rows.spec.tsx +++ b/packages/client/ui-workspace/tests/rows.spec.tsx @@ -157,6 +157,29 @@ describe('workspace browser rows', () => { expect(screen.queryByRole('button', { name: /工作区/ })).toBeNull() }) + it('blank New Session rows carry no menu, no time label, and no hover-card time', () => { + vi.useFakeTimers() + try { + const node: SessionNode = { + id: sid('s-blank'), title: 'ignored', blank: true, running: false, updatedAt: 0, + } + render() + // The placeholder has no content yet: no row verbs, no "now" stamp. + expect(screen.queryByRole('button', { name: /会话.*的操作/ })).toBeNull() + expect(screen.queryByText('刚刚')).toBeNull() + // The hover card keeps title + status but drops the timestamp line. + const wrapper = screen.getByRole('treeitem').parentElement as HTMLElement + fireEvent.pointerEnter(wrapper) + act(() => { vi.advanceTimersByTime(500) }) + expect(screen.getAllByText('新会话').length).toBeGreaterThanOrEqual(2) + expect(screen.getByText('空闲')).toBeTruthy() + expect(screen.queryByText('刚刚')).toBeNull() + } finally { + vi.useRealTimers() + } + }) + it('session row menu opens without opening the session and dispatches rename, fork, and archive', () => { const onOpen = vi.fn() const onRename = vi.fn()