Skip to content

feat(sidebar): 折叠的项目行报告该项目在跑什么(#1441) - #1453

Open
HanawaBanana wants to merge 4 commits into
vastsa:mainfrom
HanawaBanana:feat/project-running-status
Open

HanawaBanana wants to merge 4 commits into
vastsa:mainfrom
HanawaBanana:feat/project-running-status

Conversation

@HanawaBanana

@HanawaBanana HanawaBanana commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

摘要

Issue #1441 落地方案(已按作者意见改版):折叠起来的项目行只用一个数字说明「这个项目在跑什么」,具体口径在悬停时以一句只读摘要给出;定时任务的运行也算进来。数字不可点,点击行为与项目行其余部分一致。

分支 feat/project-running-status,基于 origin/main 278e929ca(pnpm check:pr-base 通过),4 个提交:

  1. feat(sidebar): report a collapsed project's running work
  2. docs(sidebar): contract the project status row and its scenario
  3. feat(sidebar): keep one number on the project row, explained on hover
  4. docs(sidebar): describe the project row's one number and its hover hint

refs #1441

改版要点(对应你那条 review)

感觉ui上面看着有点复杂了,是否可以移动到hover事件里面,如果有运行的任务,可以加一个状态指示灯

  • 行内不再铺文字:去掉了圆点与「运行中 2 · 定时任务 1 · ✓1」这类标签堆叠,只剩一个数字。
  • 数字的口径:还有工作在跑 → 在跑的数量(会话 + 定时任务运行);没有工作在跑 → 未读结果数。色调沿用会话行自己的语言:橙=运行中、紫=有会话等你回答(优先,并保留会话行那套有界脉冲,颜色不是唯一信号)、绿=全部结束待查看、红=有失败。
  • 细节进 hover(只读):悬停或用键盘聚焦该行时,行原有的提示追加完整句子 —— …/app — 运行中 2 · 定时任务 1;同一句话也写进行头的无障碍描述,屏幕阅读器无需悬停即可读到。hover 面只有摘要,没有任何可点条目。
  • 数字不是控件:没有新增点击目标,行的展开/收起语义与命中区域不变。

实现

状态聚合(渲染层,不新增查询)

  • lib/sidebar-project-status.ts:只读渲染层已有状态——该行已列出的会话、未过滤的会话列表(自动化转写仍在 store 里,只是不再列进侧栏,见 feat(scheduled): 定时任务页改造为「任务 + 运行记录」主从视图(#1291 阶段一) #1298)、runningSessions、未读 sessionOutcomes、待处理的权限/提问/Plan,以及下面的运行广播。折叠的项目因此不需要任何新的宿主读取。
  • 结果沿用会话行自己的「尚未查看」规则,所以打开某个会话会清掉它在数字里的那一份。

定时任务运行的到达与结算

  • 新增渲染层事件 pi-desktop/scheduled/event/changed,载荷 { runId, sessionId, status, taskId?, taskTitle?, projectPath? },与 sessionsChanged / todosChanged / plansChanged 同构,纯增量。
  • 三个发送时机:宿主准入运行(手动与自动派发都经 runtime/scheduled-runner.ts 公告)、运行结算(runtime/plans.ts 在 scheduled.finishRun 成功之后才公告)、派发失败(runner 的 catch 自行公告)。
  • 重载后不在事件窗口内的运行由 seed 补上:scheduled.list + 每任务最近一次运行(latestPerTask),只取 running 且带 sessionId 的(评审 nit 已收:缺 sessionId 的运行本就无法归属到项目行,直接跳过)。
  • store 里一个会话只保留一次运行(任务不会与自己重叠),这张表不随运行次数增长。

i18n:8 个键 ×9 语言(nav.projectStatus*,句子用的那些)。

取舍

验证

真机(隔离 profile + stub 模型服务,无真实凭据):同一项目两段会话 + 一个定时任务运行,折叠项目后用 CDP 读回真实 DOM。

时刻 数字 hover 提示 无障碍描述
三段都在跑 3(橙) …/app — 运行中 2 · 定时任务 1 项目状态:运行中 2 · 定时任务 1
一段先结束 2(橙) — 运行中 1 · 定时任务 1 · 已完成 1
切换项目再回来 2(橙) — 同上(会话列表刷新后不丢)
全部结束 3(绿) — 全部结束 — 3 条待查看
  • 定时任务走的是定时任务页自己的「立即运行」路径(scheduled.execute → 主进程派发 → 会话提示);模型请求 3 次,宿主库里该次运行为 completed、ended_at 已写入——确实跑完并结算,而不是停在 running。

真机截图(已按新版重拍)

1. 三段都在跑 → 数字 3

折叠项目行:只显示数字 3

2. 悬停该行 → 只读摘要

悬停后显示路径与运行摘要

3. 一段先结束 → 数字 2,摘要补上「已完成 1」

部分结束:数字 2

4. 切换项目再回来 → 数字与摘要都还在

切项目后数字仍在

5. 全部结束 → 数字 3 转绿(3 条待查看)

全部结束:数字 3 转绿

截图原图(2×,1280×900 CSS 像素)同时提交在 .github/pr-assets/project-running-status/。

自动化

  • apps/desktop/test/sidebar-project-status.test.mjs(8 例):会话数与定时任务数分开、其他项目的运行不计入、需要读者优先、全部结束、分隔符无关的路径匹配、同一次运行不重复计数、结算的运行经未读结果上报。
  • apps/desktop/test/sidebar-project-status-contract.test.mjs(7 例):侧栏 ↔ store slice ↔ API 桥 ↔ 协议事件 ↔ 主进程公告的接线;行内只有一个数字、没有 data-action、没有 statusTarget;色调优先级;提示与无障碍描述的句子;seed 只收带 sessionId 的在跑运行;一个会话一次运行。
  • apps/desktop/test/sidebar-navigation.test.mjs:项目行的路径提示断言更新为「路径(可附状态句子)+ 500ms + described-by」。
  • apps/desktop/test/scheduled-runner.test.mjs(+2 例):准入公告带任务身份;派发失败会结算自己公告过的运行。
  • 全量:node --test test/*.test.mjs → 3606 passed / 0 failed;tsc -p tsconfig.json --noEmit 通过;pnpm build:js(含 docs 构建)通过;check-locales.mjs + check-docs.mjs 通过;packages/i18n 29 例通过;check-style-tokens.mjs 与 biome lint 通过。

文档

  • 04-ux/08-component-spec.md §3.9a 已整表重写(数字/色调/句子/动效),§20A.4 补一条「一次运行如何到达项目行」,中英双语。
  • 06-delivery/04-e2e-test-plan.md:E2E-PROJECT-status-on-a-collapsed-row(步骤改为读 hover 句子)+ 可追溯性行,中英双语。
  • 08-meta/decisions-log.md:D650,中英双语。

AI 辅助说明

本 PR 的实现、文档与验证由 AI 编码代理协助完成(PI-Desktop,Agent 模式)。上面的命令、真机探针与宿主库查询都在本地实际执行过。

@vastsa

vastsa commented Oct 7, 2026

Copy link
Copy Markdown
Owner

感谢pr哦,我们看看效果

@muzimu217 muzimu217 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

按 #1441 里对齐的方案逐项核对了实现,并在本地(macOS arm64,基于 278e929ca 的隔离 worktree)跑了测试。

设计符合度:四点全部落地,且实现质量高

  • selector 聚合、不新增 store:sidebar-project-status.ts 只读渲染层已有状态(entry.sessions + 未过滤 store 列表 + runningSessions + sessionOutcomes + pending 提示三族),自动化转写与广播运行按 sessionId 去重且 store 路径优先;store 里一个会话只保留一条运行记录,表不会随运行次数膨胀。
  • 事件桥与命名族同构:pi-desktop/scheduled/event/changed(packages/shared),与 sessionsChanged/todosChanged/plansChanged 同构。三个公告点齐全——runner 准入即公告(身份来自 scheduled.run 应答,零额外读取)、结算在 scheduled.finishRun 成功之后才发(plans.ts finishTurn 的 settled 门)、派发失败在 catch 里自行公告 error。
  • 重载 seed:useAppShellRuntime 用 scheduled.list + listScheduledRuns({ latestPerTask: true }) 只补 running,失败静默降级——正是"事件 + 一次 seed",复用 Scheduled 页现有读取,没有新增查询。
  • UI:紫点优先且与运行中点不同现、aria-label 全量语义、settled/partial 文案区分,i18n 8 键 ×9 语言。

本地测试:三个新增/改动文件 node --test 26/26 通过;desktop 全量套件在本 worktree 跑得 3606 项:3602 通过、4 失败——4 条均与 PR 改动无关(jev 设置 / MCP 导入 / Meta OAuth 各 1,加 1 条复跑即过的偶发 MCP 超时):同领域文件在未改动的 main 上同样失败(本沙盒无凭据且限网);失败那条 plugin-mcp 在本 PR worktree 复跑 30/30 全过。CI 六项绿。(CI 六项绿)。

一个想确认的落差(非阻塞):#1441 里点击行为的约定是"落到运行详情,复用 #1298 的『选中任务 + 选中运行』恢复路径",但当前实现 statusTarget 只在 entry.sessions(列出的会话)里找——待审批/运行中的定时任务转写会计入摘要(allSessions 路径),点击却不会落到它(找不到目标时仅展开分组)。PR 的"未塞进这一版"清单(A3/原生通知/A7)没有列这条。是有意收窄还是遗漏?若是前者,建议在 PR 描述或 component-spec §20A.4 写明"点击落点不含自动化转写"这一边界;若是后者,projectRunningStatus 的输入里已经有 allSessions,按同一优先级补一个候选即可。

小 nit:seed 里 sessionId: run.sessionId ?? "" 会把缺 sessionId 的运行记到空键上——无害,但 continue 跳过更干净。

政策面:作者在 #1441 已说明经维护者确认适用 §15 例外,refs #1441 且方案即共识落地,我们这边没有疑虑。超出 fix/perf 窗口的政策判断仍归维护者。

@vastsa

vastsa commented Oct 7, 2026

Copy link
Copy Markdown
Owner

感觉ui上面看着有点复杂了,是否可以移动到hover事件里面,如果有运行的任务,可以加一个状态指示灯(类似于选中那个)

@HanawaBanana

Copy link
Copy Markdown
Contributor Author

好,我试一下看看效果

A collapsed project group hid every sign that work was still running, and
a scheduled run's result reached no surface outside the Scheduled route,
so a reader had to expand projects to learn whether anything was in
flight (issue vastsa#1441).

The project header now carries one status control built from state the
renderer already holds — its running conversations, scheduled runs
counted beside them, the session row's purple waiting-for-the-reader
colour, and the unread finished/failed results — so a collapsed project
costs no host read. Scheduled runs are announced by an additive
pi-desktop/scheduled/event/changed broadcast when the host admits a run,
when it settles, and when its dispatch fails; a reload seeds the live
ones from scheduled.list plus one newest run per task, and one
conversation keeps one tracked run so the map cannot grow per run.

refs vastsa#1441
The component spec gains §3.9a for the project row's status control (what
each state shows, what activating it does, and how it stays readable
without colour alone), and the Scheduled workspace notes how one run
reaches a project row.

The E2E plan gains E2E-PROJECT-status-on-a-collapsed-row with its
traceability rows, and D650 records the decision. Both languages move
together.

refs vastsa#1441
The first cut put the whole summary on the row — a dot, the running count,
the scheduled count and the unread results — which read as a second label
next to the project name. The row now shows one number instead, and the
hover hint the row already has is what explains it: the work in flight
while anything runs, otherwise the unread results, in the session row's
own running, permission and outcome tones.

The number is a label, not a control: it adds no click target, so the
header keeps its own expand/collapse semantics and a run's own transcript
no longer needs a landing spot. Waiting for the reader keeps the session
row's bounded pulse so colour is never the only signal, and the same
sentence rides in the row's accessible description for assistive tech.

refs vastsa#1441
§3.9a lists the number, its tones, the hover sentence and the
reduced-motion rule instead of a status control; §20A.4 and D650 follow,
and the E2E scenario reads the sentence on hover instead of clicking a
status. Both languages move together.

refs vastsa#1441
@HanawaBanana
HanawaBanana force-pushed the feat/project-running-status branch from fdbbefa to 4b6c0f2 Compare October 8, 2026 01:23
@HanawaBanana

Copy link
Copy Markdown
Contributor Author

已按你那条意见改完,推上来了(并已变基到最新 main 52b52f7a5):

行内只留一个数字,细节进 hover(只读)

  • 去掉了圆点和「运行中 2 · 定时任务 1 · ✓1」这类文字堆叠 —— 项目行尾现在只有一个数字。
  • 口径:还有工作在跑 → 在跑的数量(会话 + 定时任务运行);没有工作在跑 → 未读结果数。色调沿用会话行自己的语言:橙=运行中、紫=有会话等你回答(优先,保留会话行那套有界脉冲,所以颜色不是唯一信号)、绿=全部结束待查看、红=有失败。
  • hover:悬停或用键盘聚焦该行时,行原有的提示追加一句完整摘要 —— …/app — 运行中 2 · 定时任务 1。只读,没有任何可点条目;同一句话也写进行头的无障碍描述。
  • 数字不是控件:不新增点击目标,行的展开/收起语义与命中区域不变。

真机实测(折叠态,CDP 读回真实 DOM)

时刻 数字 hover 提示
三段都在跑 3(橙) …/app — 运行中 2 · 定时任务 1
一段先结束 2(橙) —
全部结束 3(绿) —

截图已全部换成新版(含 hover 那张),见上面 5 张;契约(component-spec §3.9a §20A.4、E2E 场景、D650)与 9 语言 i18n 同步更新,本地全量 node --test 3622 passed / 0 failed。

顺带回应的两点

  • muzimu217 提的「点击落点不含自动化转写」:既然行内不再有可点目标,这个落差自动消失,§20A.4 相应改为「数字覆盖这次运行、行的提示给出它的名字」。
  • 他提的 seed nit 已收:缺 sessionId 的运行不再用空串占位,直接跳过(这类运行本也无法归属到某个项目行)。

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants