fix: hide project activity summary when idle
This commit is contained in:
@@ -192,5 +192,5 @@ warming/loading/live/partial/error -> inactive
|
||||
项目计数。每个项目内先列待处理会话,再列运行中会话,同类别保留来源顺序。
|
||||
|
||||
展开项目只改变菜单层级,不导航。选中项目的活动全部结束后返回项目列表;全局活动结束后
|
||||
保留已打开的空状态和“暂无活动”入口。后台更新保留仍存在的焦点项,删除焦点项时将焦点
|
||||
移至当前层可用项或空菜单。关闭菜单后,新的活动不会自动重新打开菜单。
|
||||
关闭菜单并隐藏摘要入口。后台更新保留仍存在的焦点项,删除焦点项时将焦点
|
||||
移至当前层可用项。关闭菜单后,新的活动只恢复摘要,不会自动重新打开菜单。
|
||||
|
||||
@@ -142,7 +142,7 @@ Switch,切换后立即持久化。系统工具区域列出四项内置固定
|
||||
### FR-14 项目活动汇总
|
||||
|
||||
用户在任一项目中都能查看跨项目的运行中与待处理会话,并从活动列表直接进入所选会话。
|
||||
汇总入口常驻项目选择器下方,没有活动时显示“暂无活动”。点击后先显示有活动会话的项目,
|
||||
汇总入口仅在有运行中或待处理会话时显示在项目选择器下方,无活动时整行隐藏。点击后先显示有活动会话的项目,
|
||||
悬停或点击项目展开会话子菜单,点击会话进入该条对话。项目菜单保留原有分组与顺序,并显示
|
||||
各项目的活动数量;级联菜单内的项目顺序不随待处理或运行中数量变化。
|
||||
汇总不受当前项目、搜索条件或最近历史条数限制。计数与排除规则见
|
||||
|
||||
@@ -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 问答用例的时序失败。活动摘要改为常驻后,等待该入口出现
|
||||
|
||||
@@ -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
|
||||
层级,不因活动状态重排项目。数量为零时不显示标记,连接状态仍独立显示。
|
||||
|
||||
@@ -260,6 +260,6 @@
|
||||
- Given 鼠标从项目行移向相接的会话面板
|
||||
- Then 子菜单保持打开,点击会话正常导航
|
||||
- Given 当前项目的活动全部结束
|
||||
- Then 返回项目层并恢复可用焦点;全局无活动时摘要显示“暂无活动”,已打开菜单显示空状态
|
||||
- Then 返回项目层并恢复可用焦点;全局无活动时整行隐藏摘要并关闭菜单,新活动到来时只恢复摘要
|
||||
- When 点击外部、焦点离开、按 Tab 或隐藏侧栏
|
||||
- Then 菜单关闭,后台新活动不自动重新展开
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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(<I18nextProvider i18n={i18n}><ProjectActivityCounts attention={0} running={0} /></I18nextProvider>)
|
||||
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()
|
||||
})
|
||||
|
||||
|
||||
@@ -227,15 +227,16 @@ export function ProjectActivity({
|
||||
const [open, setOpen] = useState(false)
|
||||
const triggerRef = useRef<HTMLButtonElement>(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 (
|
||||
<>
|
||||
<button
|
||||
aria-expanded={open}
|
||||
aria-haspopup="menu"
|
||||
aria-controls={open ? id : undefined}
|
||||
aria-label={`${t('projectActivity.title')}: ${activities.length ? [t('projectActivity.attentionCount', { count: activities.length - running }), t('projectActivity.runningCount', { count: running })].join(', ') : t('projectActivity.idle')}`}
|
||||
aria-label={`${t('projectActivity.title')}: ${[t('projectActivity.attentionCount', { count: activities.length - running }), t('projectActivity.runningCount', { count: running })].join(', ')}`}
|
||||
ref={triggerRef}
|
||||
className="project-activity__summary"
|
||||
onClick={(event) => {
|
||||
@@ -250,7 +251,7 @@ export function ProjectActivity({
|
||||
}}
|
||||
type="button"
|
||||
>
|
||||
{activities.length ? <ProjectActivityCounts attention={activities.length - running} running={running} /> : <span>{t('projectActivity.idle')}</span>}
|
||||
<ProjectActivityCounts attention={activities.length - running} running={running} />
|
||||
<ChevronDown aria-hidden="true" size={14} />
|
||||
</button>
|
||||
{open && visible && (
|
||||
|
||||
Reference in New Issue
Block a user