diff --git a/docs/features/application-tool-navigation/logic-design.md b/docs/features/application-tool-navigation/logic-design.md index 9704b771..6c9b53ef 100644 --- a/docs/features/application-tool-navigation/logic-design.md +++ b/docs/features/application-tool-navigation/logic-design.md @@ -192,5 +192,5 @@ warming/loading/live/partial/error -> inactive 项目计数。每个项目内先列待处理会话,再列运行中会话,同类别保留来源顺序。 展开项目只改变菜单层级,不导航。选中项目的活动全部结束后返回项目列表;全局活动结束后 -保留已打开的空状态和“暂无活动”入口。后台更新保留仍存在的焦点项,删除焦点项时将焦点 -移至当前层可用项或空菜单。关闭菜单后,新的活动不会自动重新打开菜单。 +关闭菜单并隐藏摘要入口。后台更新保留仍存在的焦点项,删除焦点项时将焦点 +移至当前层可用项。关闭菜单后,新的活动只恢复摘要,不会自动重新打开菜单。 diff --git a/docs/features/application-tool-navigation/prd.md b/docs/features/application-tool-navigation/prd.md index c0005428..cfcf4e86 100644 --- a/docs/features/application-tool-navigation/prd.md +++ b/docs/features/application-tool-navigation/prd.md @@ -142,7 +142,7 @@ Switch,切换后立即持久化。系统工具区域列出四项内置固定 ### FR-14 项目活动汇总 用户在任一项目中都能查看跨项目的运行中与待处理会话,并从活动列表直接进入所选会话。 -汇总入口常驻项目选择器下方,没有活动时显示“暂无活动”。点击后先显示有活动会话的项目, +汇总入口仅在有运行中或待处理会话时显示在项目选择器下方,无活动时整行隐藏。点击后先显示有活动会话的项目, 悬停或点击项目展开会话子菜单,点击会话进入该条对话。项目菜单保留原有分组与顺序,并显示 各项目的活动数量;级联菜单内的项目顺序不随待处理或运行中数量变化。 汇总不受当前项目、搜索条件或最近历史条数限制。计数与排除规则见 diff --git a/docs/features/application-tool-navigation/progress.md b/docs/features/application-tool-navigation/progress.md index 2c041e7d..0df61dc2 100644 --- a/docs/features/application-tool-navigation/progress.md +++ b/docs/features/application-tool-navigation/progress.md @@ -25,9 +25,16 @@ Windows Electron 视觉 fixture 使用当前 `ProjectActivity`、生产样式和 `npm test` 在 240 秒后超时,超时前 `tests/agent-package.test.ts` 的 `verifies and installs offline dependency inventories larger than one MiB` 用例失败,未取得完整结果。 -Demo 已按最新讨论隐藏空闲摘要并移除会话行箭头;生产实现仍保留空闲摘要,尚未与 Demo 对齐。 +Demo 已按最新讨论隐藏空闲摘要并移除会话行箭头;生产空闲摘要在下述非驻留修正中对齐。 后续界面修正已移除生产会话行箭头,并为活动摘要与下方新建会话增加 `8px` 间距。 +## 2026-09-14:摘要非驻留修正 + +生产 `ProjectActivity` 在无活动时返回空内容,活动归零时关闭级联菜单;新活动只恢复摘要。 +组件回归 11 项通过,覆盖初始空闲、活动归零关闭菜单及活动恢复后不自动展开。 +App 的 `project activity integration` 定向回归 18 项通过,类型检查与 Lint 通过。 +本轮 `npm test` 在 60 秒后超时,未取得全量结果;未重新执行 Electron 视觉验证。 + ## 2026-09-14:0.13.2 候选验证 正式发布准备时再次复现 App 问答用例的时序失败。活动摘要改为常驻后,等待该入口出现 diff --git a/docs/features/application-tool-navigation/ui-design.md b/docs/features/application-tool-navigation/ui-design.md index 390ff3f1..79a6ce39 100644 --- a/docs/features/application-tool-navigation/ui-design.md +++ b/docs/features/application-tool-navigation/ui-design.md @@ -19,7 +19,7 @@ ```text 品牌 项目选择 -项目活动汇总(常驻,空闲显示“暂无活动”) +项目活动汇总(仅有活动时显示) 新对话 搜索 @@ -209,7 +209,7 @@ CPU 4% · 内存 620 MB · 桌面进程 7 对应 [FR-14](./prd.md#fr-14-项目活动汇总),状态与计数遵守 [活动逻辑](./logic-design.md#9-项目活动汇总)。 -- 全局汇总按钮常驻项目选择器下方,紧凑显示待处理与运行中数量,空闲显示“暂无活动”, +- 全局汇总按钮仅在有活动时显示在项目选择器下方,紧凑显示待处理与运行中数量,空闲时整行隐藏, 右侧显示展开箭头,与下方新建会话保留 `8px` 间距。可访问名称明确数量属于“全项目活动”。 - 项目菜单在项目行显示同口径数量,保留“本地项目 / 远程项目 / 远程通道”分组和 SSH Host 层级,不因活动状态重排项目。数量为零时不显示标记,连接状态仍独立显示。 diff --git a/docs/features/application-tool-navigation/user-stories.md b/docs/features/application-tool-navigation/user-stories.md index 59ade905..f32ececa 100644 --- a/docs/features/application-tool-navigation/user-stories.md +++ b/docs/features/application-tool-navigation/user-stories.md @@ -260,6 +260,6 @@ - Given 鼠标从项目行移向相接的会话面板 - Then 子菜单保持打开,点击会话正常导航 - Given 当前项目的活动全部结束 -- Then 返回项目层并恢复可用焦点;全局无活动时摘要显示“暂无活动”,已打开菜单显示空状态 +- Then 返回项目层并恢复可用焦点;全局无活动时整行隐藏摘要并关闭菜单,新活动到来时只恢复摘要 - When 点击外部、焦点离开、按 Tab 或隐藏侧栏 - Then 菜单关闭,后台新活动不自动重新展开 diff --git a/src/renderer/src/App.test.tsx b/src/renderer/src/App.test.tsx index 4288a7d1..28050c17 100644 --- a/src/renderer/src/App.test.tsx +++ b/src/renderer/src/App.test.tsx @@ -1285,9 +1285,8 @@ describe("App", () => { vi.mocked(api.tasks.list).mockResolvedValue([{ ...task, status: "completed" }]); act(() => conversationQueueChangeListener?.(task.conversationId!)); - await waitFor(() => expect(summary).toHaveTextContent("暂无活动")); - expect(screen.getByRole("menu", { name: "全项目活动" })).toHaveTextContent("暂无运行中或待处理会话"); - fireEvent.keyDown(document.activeElement!, { key: "Escape" }); + await waitFor(() => expect(summary).not.toBeInTheDocument()); + expect(screen.queryByRole("menu", { name: "全项目活动" })).not.toBeInTheDocument(); fireEvent.click(screen.getByRole("button", { name: "当前项目" })); expect(within(screen.getByRole("menu")).getByRole("menuitemradio", { name: /Background project/u })) .not.toHaveTextContent("个运行中"); @@ -1517,8 +1516,8 @@ describe("App", () => { : { requestId: request.requestId, type: "error", status: "failed", message: "Activity execution failed" }); conversationQueueChangeListener?.(request.conversationId!); }); - await waitFor(() => expect(screen.getByRole("button", { name: /全项目活动/u })).toHaveTextContent("暂无活动")); - expect(menu).toHaveTextContent("暂无运行中或待处理会话"); + await waitFor(() => expect(screen.queryByRole("button", { name: /全项目活动/u })).not.toBeInTheDocument()); + expect(menu).not.toBeInTheDocument(); }); }); diff --git a/src/renderer/src/ProjectActivity.test.tsx b/src/renderer/src/ProjectActivity.test.tsx index def44158..4b6d3a4c 100644 --- a/src/renderer/src/ProjectActivity.test.tsx +++ b/src/renderer/src/ProjectActivity.test.tsx @@ -37,13 +37,10 @@ function open(): HTMLButtonElement { afterEach(() => { cleanup(); vi.restoreAllMocks() }) describe('ProjectActivity', () => { - it('keeps an idle summary and an accessible empty menu while hiding zero counts', () => { + it('hides the idle summary and zero counts', () => { render(view([])) - const trigger = open() - expect(trigger).toHaveTextContent('No activity') - expect(screen.getByRole('menu')).toHaveTextContent('No running conversations') - fireEvent.keyDown(document.activeElement!, { key: 'Escape' }) - expect(trigger).toHaveFocus() + expect(screen.queryByRole('button', { name: /All project activity/ })).not.toBeInTheDocument() + expect(screen.queryByRole('menu')).not.toBeInTheDocument() const { container } = render() expect(container).toBeEmptyDOMElement() }) @@ -165,10 +162,10 @@ describe('ProjectActivity', () => { rerender(view(activities.filter((activity) => activity.projectId === 'local'))) expect(screen.getByRole('menuitem', { name: /Local/ })).toHaveFocus() rerender(view([])) - expect(screen.getByRole('menu')).toHaveTextContent('No running conversations') - fireEvent.keyDown(document.activeElement!, { key: 'Escape' }) + expect(screen.queryByRole('button', { name: /All project activity/ })).not.toBeInTheDocument() expect(screen.queryByRole('menu')).not.toBeInTheDocument() rerender(view()) + expect(screen.getByRole('button', { name: /All project activity/ })).toHaveAttribute('aria-expanded', 'false') expect(screen.queryByRole('menu')).not.toBeInTheDocument() }) diff --git a/src/renderer/src/ProjectActivity.tsx b/src/renderer/src/ProjectActivity.tsx index 38e508dc..cb747472 100644 --- a/src/renderer/src/ProjectActivity.tsx +++ b/src/renderer/src/ProjectActivity.tsx @@ -227,15 +227,16 @@ export function ProjectActivity({ const [open, setOpen] = useState(false) const triggerRef = useRef(null) const id = useId() - if (!visible && open) setOpen(false) + if ((!visible || activities.length === 0) && open) setOpen(false) const running = useMemo(() => activities.filter((activity) => activity.status === 'running').length, [activities]) + if (activities.length === 0) return null return ( <> {open && visible && (