From 475fd3a8d697e313d8b50ea4c527a6f3ebba3edf Mon Sep 17 00:00:00 2001 From: weichen Date: Sat, 10 Oct 2026 08:02:21 +0000 Subject: [PATCH] Open previews from the empty state and restore the last conversation on reload Why: reproduced with headless Chromium - clicking an artifact row with no conversation selected closed the workspace overlay and nothing else appeared. requestPreview stores tabs under currentConvId ?? '_' while the render side required a non-null currentConvId, so the preview pane never mounted in the empty state. The empty state was itself a trap: currentConvId was not persisted, so every reload landed there. Evidence: vitest 85/85 (new preview-empty-state tests for the key contract and orphan-tab merge); browser replay on the fixed build shows the preview pane mounting with rendered docx content from the empty state and the conversation restoring after reload (screenshots in the Dev task card). Impact: previewTabsKey() unifies the read/write key; entering a conversation adopts orphan '_' tabs; the last conversation id persists and restores once on boot; deliverable cards toast when the artifact is gone instead of silently doing nothing. Refs: Dev task feature/empty-state-preview-fix; diagnosis session 2026-10-10. --- src/App.tsx | 8 +++---- src/components/Conversation.tsx | 6 ++++- src/components/PreviewPane.tsx | 7 +++--- src/previewtabs.ts | 20 ++++++++++++++++ src/store.ts | 41 ++++++++++++++++++++++++++++---- test/preview-empty-state.test.ts | 30 +++++++++++++++++++++++ 6 files changed, 99 insertions(+), 13 deletions(-) create mode 100644 test/preview-empty-state.test.ts diff --git a/src/App.tsx b/src/App.tsx index 63208cd..e919916 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,6 +9,7 @@ import { store, useStore } from './store' import { TaskSidebar } from './components/TaskSidebar' import { Conversation } from './components/Conversation' import { PreviewPane } from './components/PreviewPane' +import { previewTabsKey } from './previewtabs' import { Workbench } from './components/Workbench' import { Workspaces, workspaceShortcuts } from './components/Workspaces' import { AgentTracker } from './components/AgentTracker' @@ -30,10 +31,9 @@ export function App() { }, []) const ui = useStore(s => s.ui) - const previewOpen = useStore(s => { - const id = s.currentConvId - return id ? (s.previewTabs[id]?.length ?? 0) > 0 : false - }) + // 预览键与 requestPreview 的落点一致(previewTabsKey):空态(未选会话)点开 + // 工件也要渲染预览区,否则点击只剩工件弹层关闭、毫无反馈。 + const previewOpen = useStore(s => (s.previewTabs[previewTabsKey(s.currentConvId)]?.length ?? 0) > 0) const toast = useStore(s => s.toast) const trackerRunId = useStore(s => s.ui.trackerRunId) const previewExpanded = previewOpen && ui.previewExpanded diff --git a/src/components/Conversation.tsx b/src/components/Conversation.tsx index 568e464..fc13d81 100644 --- a/src/components/Conversation.tsx +++ b/src/components/Conversation.tsx @@ -124,7 +124,11 @@ function DeliverableCard({ artifact, updates = 0 }: { artifact: { artifactId: st const name = artifact.name || art?.name || '文件' const icon = artifactIcon(name, artifact.kind ?? art?.kind) const size = humanSize(art?.size) - const open = () => { if (art) store.requestPreview(art) } + const open = () => { + if (art) { store.requestPreview(art); return } + // 工件已不在当前项目(被删/跨项目残留):给一句话反馈,不再无声无息。 + store.toast(`「${name}」已不在当前项目中,无法预览`) + } return (