Skip to content

feat(web-shell): make Session Workflow dependencies navigable and quiet its chrome - #10938

Merged
yiliang114 merged 32 commits into
mainfrom
feat/session-workflow-cockpit-refresh
Sep 13, 2026
Merged

yiliang114 merged 32 commits into
mainfrom
feat/session-workflow-cockpit-refresh

Conversation

@yiliang114

@yiliang114 yiliang114 commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

What this PR does

Closes the navigation, shape and documentation gaps left in the Session Workflow surface after #8583, and takes a design pass over the plan DAG and the inspector chrome.

The plan DAG leads with the step, not its status. Read against the three questions a plan graph is opened with — what is running, what is blocked on what, what needs me — the node answered none of them first.

before                         after
┌──────────────────────┐       ┌──────────────────────┐
│ ● step-3  Blocked    │       │▌ 3  Compare findings │
│ Compare findings     │       │    2 agents    1m14s │
│ Depends on: step-1,  │       └──────────────────────┘
│   step-2             │
└──────────────────────┘       ▌ 3px status rule, absent when waiting
   border tinted per status       border stays --border for every status

Content leads. The node led with a glyph, the raw Todo id, then the status word; the only element saying what the step is came third. The id leaves the node face entirely — it is an address, so it stays in the detail panel where it is copied — and the step number takes its place, because the inspector list and the dependency chips already address steps by number and the three surfaces have to agree.

Status is stated once, as a left rule, and only when it is worth saying. Waiting is the resting state of most nodes in a real plan and carries no rule; the stylesheet already made this argument for blocked, and extending it consistently is what stops the canvas reading as a wall of tinted boxes and gives the one running node something to stand out against. Attention folds into the same rule and outranks the status tone, since it is the state the reader is asked to act on. Status is colour, which assistive tech cannot read, so it stays in the accessibility tree as words.

Time becomes visible. The face carries the agent count and the summed agent runtime for that step — the "is this alive or hung" signal, which is the first thing asked of a live run.

Dependencies are stated once. The Depends on … row renders only when no edge states it: above MAX_RENDERED_PLAN_EDGES the graph draws no edges at all, and there the row is the dependency's only statement, so deleting it outright would have lost information in exactly the case that needs it most. The input port dot goes with it — the left edge now carries the status rule, and an incoming edge already terminates in an arrowhead, which is the input marker. The output port stays. Both ports also drop --agent-blue-500: every node with an edge had one, so a permanently blue dot across most of the graph left selection no colour of its own.

Edge routing, the layering algorithm, the return lanes and the measure pass are untouched. This is the node face and the colour rule. The design plan, including the two revisions the review pass forced, is in docs/plans/2026-09-04-session-workflow-dag-design.md.

Dependencies become navigation. A step's dependencies rendered as todo.blockedBy!.join(', ') — raw ids, in the inspector detail and in the graph's own step-details panel. An id is an address, so following an edge meant reading step-3, step-7 and then finding those rows by eye, in a panel whose entire subject is which step waits on which. Both places now render each reference as the step it points at — number and title — as a control that selects it. The chips on the node face are the one exception and stay read-only: that row lives inside the node's own <button>, and a nested button is invalid HTML.

Recent activity stops contradicting itself. The list sliced to 6 while the count beside it reported the true total, so the remainder was unreachable. The cap stays as a preview and gains a control that opens the rest.

The lane reservation is bound to the constant that places lanes. .dagCanvas reserved bottom padding for layer-spanning return lanes with a literal 9px while TS placed them at lane * EDGE_LANE_HEIGHT. Raising the constant alone left the reservation short; the clamp then pinned the outer lanes together and spanning edges overlapped again — the defect the lanes exist to fix — with no test signal, because the lane test mocks offsetHeight. TS now publishes the pitch and the stylesheet reads it, with no second copy as a var() fallback.

The graph narrows at two viewport steps. Three 240px lanes and two 64px gutters are ~850px, so the canvas scrolled in both axes at once and the horizontal scroll hid the layer the vertical scroll was looking for. Below 720px the lane and gutter narrow, and below 480px they narrow again, which gets a three-layer plan under a phone viewport. Deeper plans still scroll horizontally — that is the honest limit of narrowing alone, and reusing the flat layout stays with #10866. Layer semantics and the rect-measured edge routing are untouched.

The auto-sized dialog keeps its gutter. No longer in this diff. The DialogShell gutter fix reached main separately via #11077, so the current 13-file diff touches no dialog code. Struck rather than deleted because the review rounds below still refer to it.

The inspector chrome states each thing once. Section labels were tracked-out uppercase that shouted over the content they introduced; the cockpit spent the selection accent on a fixed eyebrow, making the one blue thing on the page the one thing that never changes; completed-over-total sat in a tinted pill directly above the progress bar drawing the same ratio and beside the status word naming the same state; and four meta strings were glued with middle dots, wrapping as a unit and reading out as one run of text. No token or theme values change — this is the existing system applied more sparingly.

todo_write's ungated dependency semantics are documented. No longer in this diff. The comment-only todo_write commit (ff4b4907) is in neither this branch's diff nor main, so nothing here documents those semantics. The rationale survives in the review history; the diff no longer carries it.

Round-1 review fixes are folded in: the show-all control's specificity collision, the step-details panel's raw ids, the overstated narrow-viewport claim and its test name, the duplicated lane-pitch fallback, and a comment antecedent. Details under Risk & Scope.

Why it's needed

These are the still-open items from #8583's review that are behaviour, shape or documentation rather than contract decisions, plus #10866.

Verified against main first, and deliberately not re-litigated here: R1-11, R1-21, R1-22, R1-25, R1-26, R1-28, R1-36, R5-5 and the sibling-fan-out ordering are already fixed there. R1-41 is moot — the skills chip it covered no longer exists on this surface.

Reviewer Test Plan

How to verify

  • Open a session with an approved plan that has blockedBy dependencies. Expect each graph node to read <number> <title> with the agent count and elapsed beneath, and a left rule only when running/completed/attention. The visible Depends on row is off while edges are drawn, with two corrections: the cockpit (showStepDetails={false}) does render the chips by design, and in the interactive graph the dependency is still stated to assistive tech by an sr-only summary on the node button (4860e0a7e6), because the drawn edges are aria-hidden.
  • Select a node and open its step-details panel. Expect Depends on and Unblocks to render each reference as <number> <title>, and clicking one to select that step. Expect the same in the inspector detail.
  • With more than six linked Agent runs, expect six rows plus "Show all N runs" rendered as a full-width single-line control, not wrapped into a narrow column; expect it to disappear once expanded and the count to match the summary.
  • Force a plan past MAX_RENDERED_PLAN_EDGES (500 dependencies). Expect no edges, the existing "Too many dependencies to draw" notice, and the Depends on chips back on each node — that is the case where the row is the only statement of the dependency.
  • Narrow to ~700px and then to ~390px with a three-layer plan; expect the lanes to narrow at each step. Measured fit: 700px fits exactly (3x168 + 2x32 = 676 against 676) and 430px fits (3x116 + 2x18); at 390px the scroller is 366px wide against 396px of content, so a three-layer plan still scrolls about 30px (414px: about 6px; it fits from roughly 420px). The dialog-gutter check is dropped: that fix is on main via fix(web-shell): keep the auto-sized dialog's gutter on a phone #11077, not in this diff.
  • Check the status rule's contrast in both themes, and that two meta facts do not crowd a 116px lane at the narrowest step.

Evidence (Before & After)

The ASCII before/after under "What this PR does" is the node-face change. CI's Capture web-shell visuals job renders workflow-page-* in both themes on every push and posts the composites to this PR; note that suite does not cover the inspector panel, so the inspector changes have no rendered evidence here.

Local runs are focused suites only, Node 22, linux:

  • client/components/messages/PlanExecutionView (+ .css.test.ts) — 45/45
  • client/components/workflow (+ client/components/workflows/WorkflowRunsPage) — 39/39; the combined vitest run client/components/messages/PlanExecutionView client/components/workflow is 9 files, 84 tests
  • DialogShell and packages/sdk-typescript/test/unit/daemonUi.test.ts are no longer run for this PR: neither is in the diff (the gutter fix is on main via fix(web-shell): keep the auto-sized dialog's gutter on a phone #11077; the daemonUi.test.ts keep-set commit 3863bb68 is in neither the diff nor main)
  • All counts above were measured before the main merge in 5c4f1de9e8; CI on the merged ref is the authority

Every behavioural change is mutation-checked (red on revert, green restored): the joined-id dependency lists, the silent activity cap, ungating the node's dependency row, deleting the node's accessible status word, dropping the elapsed, removing the step-details link handler, the endYrouteY path-tail substitution, and removing 'skills' from the compactor keep-set each fail exactly their own assertion. One probe initially survived — Running also appears in an execution row inside the node — and the assertion was tightened to a node with no linked agent before it killed the mutant.

Three geometry tests identified a node by its first <span>, which was the Todo id and is now the accessible status word; they bind to data-plan-node-id instead, which is the stable hook and was always the better one.

Tested on

OS Status
🍏 macOS ⚠️ not tested
🪟 Windows ⚠️ not tested
🐧 Linux

Environment (optional)

Unit tests only; no local build or whole-package typecheck was run, and no browser is available in that environment — CI owns both.

Risk & Scope

  • Main risk or tradeoff: the visual work was read from the stylesheet, not rendered. Round 1 caught the cost of that directly — .showAllActivity was a single class (0,1,0) losing to .activityList > button (0,1,1), which auto-placed its label into the 52px first column of the activity grid. It is fixed with the .activityList > prefix plus explicit display/grid-template-columns resets, and both it and the earlier .metrics collision are now pinned by the stylesheet guard so neither can regress to a bare class. A real render before merge is still the verification I would not skip, particularly the status rule's contrast in dark mode.
  • Not validated / out of scope: the R5-2 / R5-6 / R6-4 design-ledger threads from feat(web-shell): add an experimental session workflow cockpit #8583, which are public-API and contract choices awaiting a maintainer decision rather than defects; feat(web-shell): make the session workflow DAG navigable #10866's showStepDetails and keyboard-traversal items, which are not in this diff — hence Refs rather than Closes; and the deferred test-coverage items R1-13, R1-24, R1-29 and R1-43.
  • Breaking changes / migration notes: none. The DialogShell gutter fix is no longer part of this diff — it is on main via fix(web-shell): keep the auto-sized dialog's gutter on a phone #11077. The node face changes what is displayed, not what is measured. Edge geometry is changed now, by the R6-3 fix in 4860e0a7e6: the adjacent-layer router's shoulder went from Math.max(24, run / 2) to run / 2, which is identical for every gutter wide enough to afford the 24px floor (so the desktop curve and its pinned path M 214 50 C 255 50, 255 50, 296 50 are unchanged) and otherwise shortens a shoulder the narrow tiers could not afford. The todo_write commit is no longer in this diff either.

Linked Issues

Refs #8583, #10866.

中文说明

这个 PR 做了什么

补上 #8583 之后 Session Workflow 面上遗留的导航、形态和文档缺口,并对 plan DAG 与 inspector 的界面做一次设计收敛。

plan DAG 改成以步骤开头,而不是以状态开头。 对着打开一张 plan 图时要回答的三个问题——什么在跑、谁卡在谁上面、哪个需要我处理——节点一个都没有优先回答。

改前                            改后
┌──────────────────────┐       ┌──────────────────────┐
│ ● step-3  Blocked    │       │▌ 3  Compare findings │
│ Compare findings     │       │    2 agents    1m14s │
│ Depends on: step-1,  │       └──────────────────────┘
│   step-2             │
└──────────────────────┘       ▌ 3px 状态竖条,等待态不画
   边框按状态染色                  边框对所有状态统一用 --border

内容优先。原来节点先出状态字形、再出裸 Todo id、再出状态词,唯一说明这一步是什么的元素排在第三。id 完全离开节点面——它是地址,所以留在你真正会去复制它的详情面板里——由步骤号取而代之,因为 inspector 列表和依赖 chip 本来就按号寻址,三个面必须一致。

状态只说一次,用左侧竖条承载,而且只在值得说的时候说。等待是真实 plan 中大多数节点的静息态,不画竖条;样式表本来就为 blocked 写过这个论证,把它一致地推广开,正是让画布不再像一堵染色方块墙、让唯一在跑的那个节点有东西可以反衬的前提。attention 并进同一根竖条并压过状态色,因为它才是要读者动手的那个状态。状态是颜色,读屏读不到,所以状态词保留在无障碍树里。

时间变得可见。节点面上现在带该步骤的 agent 数和 agent 运行时长合计——也就是"这东西还活着还是挂了"的信号,那是看实时运行时的第一个问题。

依赖只说一次。Depends on … 那一行只在没有边表述它时才渲染:依赖数超过 MAX_RENDERED_PLAN_EDGES 时图根本不画边,那时这一行是依赖的唯一表述,直接删掉恰恰会在最需要它的场景丢失信息。输入端口圆点随之去掉——左边现在是状态竖条,而入边本来就以箭头收尾,那个箭头就是输入标记;输出端口保留。两个端口点也都不再用 --agent-blue-500:之前每个有边的节点都挂一个,于是图上大部分地方常驻一个蓝点,选中色等于没有自己的颜色。

边路由、分层算法、回程泳道和测量 pass 都没有改动。本次改的是节点面和颜色规则。design plan(含评审那一遍强制改掉的两处)在 docs/plans/2026-09-04-session-workflow-dag-design.md

依赖变成导航。 一个步骤的依赖原本渲染为 todo.blockedBy!.join(', ')——裸 id,出现在 inspector 详情和图自己的 step-details 面板里。id 是地址,所以顺着一条边走意味着先读 step-3, step-7,再用眼睛去找那些行,而这个面板的全部主题恰恰就是谁在等谁。这两处现在都把每个引用渲染成它指向的那个步骤——号码加标题——并作为点击即选中的控件。节点面上的 chip 是唯一例外,保持只读:那一行位于节点自己的 <button> 内部,嵌套 button 是非法 HTML。

最近活动不再自相矛盾。 列表截断到 6 条,而旁边的计数报的是真实总数,于是其余部分无法触达。上限保留为预览,并新增一个展开其余部分的控件。

泳道预留绑定到放置泳道的那个常量。 .dagCanvas 用字面量 9px 为跨层回程泳道预留底部内边距,而 TS 按 lane * EDGE_LANE_HEIGHT 放置它们。只调高常量会让预留不足,随后 clamp 把外侧泳道压在一起、跨层边再次重叠——正是泳道存在要解决的那个缺陷——而且没有任何测试信号,因为泳道测试直接 mock 了 offsetHeight。现在由 TS 下发间距、样式表读取,且不再以 var() 兜底的形式留第二份拷贝。

图在两级视口断点收窄。 三条 240px 泳道加两个 64px 间隙约 850px,于是画布同时在两个轴上滚动,横向滚动把纵向滚动正在找的那一层挡住了。720px 以下泳道和间隙收窄,480px 以下再收窄一次,使三层的 plan 能放进手机视口。更深的 plan 仍会横向滚动——这是单靠收窄能达到的诚实上限,改用平铺布局仍归 #10866。分层语义和基于实测矩形的边路由都未改动。

自适应尺寸的对话框保住了它的边距。 已不在本 diff 中。 DialogShell 的边距修复已经通过 #11077 单独进入 main,所以当前 13 个文件的 diff 不涉及任何对话框代码。之所以划掉而不是直接删除,是因为下面的评审轮次仍然引用它。

inspector 的界面元素每件事只说一次。 区块标签原本是加了字距的全大写,盖过它所引出的内容;cockpit 把选中强调色花在一个固定 eyebrow 上,使页面上唯一的蓝色成了唯一永不变化的东西;完成数/总数装在一枚染色药丸里,而正下方的进度条画的是同一个比例、左边的状态词说的是同一个状态;还有四处 meta 串用中点粘成一体,会作为整体折行、并被读屏当成一串连续文本读出。没有改动任何 token 或主题值——这是把现有系统用得更克制。

记录 todo_write 依赖语义不受开关控制的原因。 已不在本 diff 中。 那次纯注释的 todo_write 提交(ff4b4907)既不在本分支的 diff 里,也不在 main 上,所以这里没有任何内容在记录那些语义。论证过程保留在评审历史里,diff 不再携带它。

第一轮评审的修复已并入:show-all 控件的特异度冲突、step-details 面板的裸 id、说过头的窄视口结论及其测试名、重复的泳道间距兜底,以及一处注释指代。细节见 Risk & Scope。

为什么需要

这些是 #8583 评审中仍然开着、且属于行为、形态或文档而非契约决策的条目,加上 #10866

先对着 main 核对过、并刻意不在此重做的:R1-11、R1-21、R1-22、R1-25、R1-26、R1-28、R1-36、R5-5 以及 sibling 扇出的顺序问题,均已在 main 上修复。R1-41 已失效——它覆盖的 skills chip 在这个面上已不存在。

评审者验证计划

如何验证

  • 打开一个带 blockedBy 依赖、已批准 plan 的 session。每个图节点应读作 <号码> <标题>,下方是 agent 数与耗时;只有 running/completed/attention 才有左侧竖条。画了边时可见的 Depends on 行是关掉的,但有两处更正:cockpit(showStepDetails={false})按设计确实会渲染 chip;而在交互图里,依赖仍然通过节点按钮上的 sr-only 摘要表述给辅助技术(4860e0a7e6),因为画出来的边是 aria-hidden
  • 选中一个节点并打开它的 step-details 面板。Depends onUnblocks 的每个引用应渲染为 <号码> <标题>,点击应选中该步骤。inspector 详情中同理。
  • 关联的 Agent 执行超过六条时,应显示六行加上 "Show all N runs",且该控件应是整行的单行控件,而不是被压进窄列折行;展开后控件消失,计数与摘要一致。
  • 构造一个超过 MAX_RENDERED_PLAN_EDGES(500 条依赖)的 plan。应看不到边、看到既有的 "Too many dependencies to draw" 提示,并且每个节点上的 Depends on chip 回来了——那正是这一行是依赖唯一表述的场景。
  • 把视口收到约 700px 再收到约 390px,用三层的 plan:泳道应在每一级收窄。实测适配情况:700px 恰好放得下(3x168 + 2x32 = 676 对 676),430px 也放得下(3x116 + 2x18);390px 下滚动容器宽 366px 而内容 396px,所以三层 plan 仍会横向滚动约 30px(414px 约 6px,大约从 420px 起才放得下)。对话框边距那一项检查已删除:该修复在 main 上(fix(web-shell): keep the auto-sized dialog's gutter on a phone #11077),不在本 diff 中。
  • 检查状态竖条在深浅两个主题下的对比度,以及最窄一级的 116px 泳道里两个 meta 事实会不会挤。

证据(改前 / 改后)

"这个 PR 做了什么"下方的 ASCII 对比即节点面的改动。CI 的 Capture web-shell visuals job 每次推送都会在深浅两个主题下渲染 workflow-page-* 并把对比图贴到本 PR;注意该套件不覆盖 inspector 面板,所以 inspector 的改动在此没有渲染证据。

本地只跑了聚焦套件,Node 22,linux:

  • client/components/messages/PlanExecutionView(含 .css.test.ts)—— 45/45
  • client/components/workflow(含 client/components/workflows/WorkflowRunsPage)—— 39/39;合并跑 vitest run client/components/messages/PlanExecutionView client/components/workflow 为 9 个文件、84 个用例
  • 本 PR 不再跑 DialogShellpackages/sdk-typescript/test/unit/daemonUi.test.ts:两者都不在 diff 里(边距修复已在 main,见 fix(web-shell): keep the auto-sized dialog's gutter on a phone #11077daemonUi.test.ts 的 keep-set 提交 3863bb68 既不在 diff 也不在 main
  • 以上数字都是 5c4f1de9e8 合并 main 之前测的;合并后的状态以 CI 为准

每一处行为改动都做了变异验证(回滚即红、恢复即绿):拼接式 id 依赖列表、静默的活动上限、解除节点依赖行的 gate、删除节点的无障碍状态词、去掉耗时、移除 step-details 的点击处理、把路径尾部的 endY 换成 routeY、以及从 compactor keep-set 中移除 'skills',各自精确打红对应断言。有一个探针最初存活——Running 在节点内的执行行里也会出现——把断言收紧到一个没有关联 agent 的节点之后才杀死该变异体。

三个几何测试原本靠节点里第一个 <span> 来识别节点,那时是 Todo id,现在是无障碍状态词;它们改绑到 data-plan-node-id,那本来就是更稳的钩子。

测试环境

系统 状态
🍏 macOS ⚠️ 未测试
🪟 Windows ⚠️ 未测试
🐧 Linux

运行环境(可选)

仅单元测试;没有在本地跑构建或整包 typecheck,该环境也没有浏览器——这两项由 CI 负责。

风险与范围

  • 主要风险或权衡: 视觉部分是读样式表推导的,没有真实渲染。第一轮评审直接暴露了这个代价——.showAllActivity 是单类 (0,1,0),输给 .activityList > button (0,1,1),导致它的标签被自动放进活动网格 52px 的第一列。现已通过加 .activityList > 前缀并显式重置 display/grid-template-columns 修复,并且它和先前的 .metrics 冲突都已被样式表守卫钉住,两者都不能退回裸类。合并前做一次真实渲染仍是我不会省的验证,尤其是状态竖条在深色模式下的对比度。
  • 未验证 / 不在范围: feat(web-shell): add an experimental session workflow cockpit #8583 的 R5-2 / R5-6 / R6-4 设计台账线程,它们是公开 API 与契约选择,等待维护者裁定而非缺陷;feat(web-shell): make the session workflow DAG navigable #10866showStepDetails 与键盘遍历两项不在本 diff 内——因此用 Refs 而非 Closes;以及延期的测试覆盖项 R1-13、R1-24、R1-29、R1-43。
  • 破坏性变更 / 迁移说明: 无。DialogShell 的边距修复已不属于本 diff —— 它在 main 上(fix(web-shell): keep the auto-sized dialog's gutter on a phone #11077)。节点面改变的是显示内容而非测量内容。边的几何现在确实改了,来自 4860e0a7e6 里的 R6-3 修复:相邻层路由的肩宽从 Math.max(24, run / 2) 改为 run / 2;对任何容得下 24px 下限的间隙两者完全等价(所以桌面端曲线及其钉住的路径 M 214 50 C 255 50, 255 50, 296 50 未变),其余情况只是把窄断点容不下的肩宽缩短。todo_write 那次提交也已不在本 diff 中。

关联 Issue

Refs #8583, #10866.

yiliang114 added 4 commits September 4, 2026 00:42
The `auto` size floors the panel at `min-w-[min(100%,560px)]` so a small plan
graph does not collapse to a narrow column. tailwind-merge keeps that class
alongside the base ceiling `max-w-[calc(100%-2rem)]` — different modifier
groups — and below the `sm:` breakpoint min-width wins over max-width. On a
390px viewport the Plan & tasks dialog therefore rendered its rounded, ringed
panel flush to both screen edges, while every fixed size (sm/md/lg/xl) kept
the 1rem gutter at that same width.

Floor the panel at the gutter the ceiling already reserves.
…aces lanes

Lane spacing was stated twice in two languages: `EDGE_LANE_HEIGHT` in TS,
which places each layer-spanning return lane at `lane * EDGE_LANE_HEIGHT`,
and a literal `9px` in `.dagCanvas`'s bottom padding, which reserves room for
them. The two are coupled only through the `--plan-edge-lanes` inline style,
so raising the constant alone leaves the reservation short; with enough
spanning edges the clamp pins the outer lanes together and layer-spanning
edges overlap again — the exact defect the lanes exist to fix. Nothing would
have caught it: the lane test mocks `offsetHeight` directly.

TS now publishes the pitch as `--plan-edge-lane-height` beside the lane count,
and the stylesheet reads it.

Also give the graph a phone: three 240px lanes plus two 64px gutters are
~850px, so the canvas scrolled in both axes at once and the horizontal scroll
hid the layer the vertical scroll was looking for. Below 720px the lane and
gutter narrow. Layer semantics and the rect-measured edge routing are
untouched, so this changes what fits, not what is drawn.

The new stylesheet guard pins both, in the shape the other `.css.test.ts`
files use, since jsdom does not compute the cascade.

One test-only addition on the same component: the spanning-edge test fixed
the edge's arrival column (`H 876`) but not its arrival row, and nothing else
in the file asserted a spanning edge's `endY`. Substituting the return lane's
`routeY` for `endY` in the path tail draws the edge ~170px below the target
node while every existing assertion still passes. Pinned corner-agnostically.

Refs #10866.
`compactTaskExecutionOutput` keeps an allowlist of `task_execution` fields
when it drops the bulky ones, and every neighbouring entry — `executionSummary`,
`tokenCount` — is already pinned by the reducer tests. `skills` was not:
removing it from the keep-set left the whole suite green, and a session
restored from a persisted transcript would silently lose the skill list the
live run recorded.

Mutation-checked: deleting `'skills'` from the keep-set fails exactly this
assertion (1 failed | 327 passed); restored, 328/328.
…rface state each thing once

Two related passes over the Session Workflow inspector and cockpit.

## Dependencies become navigation

A step's dependencies rendered as `todo.blockedBy!.join(', ')` — a run of raw
Todo ids, in both the graph node and the inspector detail. An id is an
address, not a label, so following an edge meant reading `step-3, step-7` and
then finding those rows by eye, in a panel whose entire subject is which step
waits on which.

Each reference is now the step it points at — the step number the list and
the graph both already show, plus its title — as a control that selects that
step. The dependency list becomes the graph's navigation.

Recent activity was sliced to 6 while the count beside it reported the true
total, so the panel contradicted itself and the remainder was unreachable.
The cap stays as a preview and gains a control that opens the rest.

## The surface states each thing once

The panel had accumulated chrome that competed with its content:

- Section labels were tracked-out uppercase at 10-11px, so every fixed label
  shouted over the step title and status it introduced. They are labels, not
  headlines: sentence case at a muted weight.
- The cockpit spent the accent colour on a fixed "Dependency graph" eyebrow
  above the session title — making the one blue thing on the page the one
  thing that never changes, while the graph below uses that same accent to
  mean "this node is selected". The label moves to the subtitle line and the
  accent is left to mean selection.
- Completed-over-total sat in a tinted pill directly above the progress bar
  that draws the same ratio, beside the status word that names the same
  state — three surfaces for one fact. The count keeps the tone as ink and
  drops the pill.
- Runtime, tool calls, tokens and artifact kind/status were glued into single
  strings with middle dots, which wrapped as a unit and read out as one run of
  text. They are separate facts, so they are separate elements.

The identity line keeps the mono face only on the short session id, which is
an address the user copies or matches against a daemon log.

No token or theme values change; this is the existing system applied more
sparingly.

Both behaviour changes are mutation-checked: reverting the dependency list to
the joined id string, and restoring the silent cap, each fail exactly their
new test.

Refs #10866.
@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Qwen Triage finishedview run. See the stage comments in this thread for the result.

Qwen Triage 已完成 —— 查看运行。结果见本线程中的各阶段评论。

@yiliang114
yiliang114 force-pushed the feat/session-workflow-cockpit-refresh branch from 8ab5c5e to f2c818f Compare September 3, 2026 15:47
@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration.

中文

请勿对活跃的 PR 执行 rebase 或 force-push,因为这会使已有的评审评论失效。另外,供日后参考:作为集成流程的一部分,机器人始终会自动将所有改动压缩(squash)为单个提交。

…flag

`todo_write`'s omitted-`blockedBy` preservation, and the `blockedBy` schema
text that invites the omission, both reach sessions with Session Workflow
disabled. That was a deliberate choice when the behaviour landed, but nothing
in the file said so, so it reads as a missed gate — the `hasActivePlan`
derivation sat bare and the only nearby comment explains the dangling-edge
filter, not the gating.

Record the decision and its reasoning: these are plan data-model semantics
rather than presentation, and gating them on a visualization switch would
give the tool two dialects, storing different plans for the same call
depending on whether anyone is looking at the graph.

Record the blast radius in the same place, because "reaches every session"
overstates it. The merge changes an update only when the previous plan still
has an unfinished item, the same id carried a non-empty `blockedBy`, and this
call omits it. Plans that never used dependencies are untouched
(`undefined?.filter()` is `undefined`), callers that always send the full list
are unaffected, and `[]` stays the explicit clear. What does reach every
session is the schema text, which is why it is named as widening this
branch's exposure rather than as an independent change.

Comment-only; no behaviour, no signatures.
@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Re-run against head 132f2b27. The previous gate ran on 2026-09-03 against ff4b4907, so almost nothing below carries over — this is a fresh pass on a much-changed tree.

Template looks good ✓ — every required heading is there, plus the Chinese <details>.

Problem: observed, not theoretical. Each item is something you can point at in the pre-diff source: dependencies rendered as todo.blockedBy!.join(', ') (raw ids, in two panels), the activity list sliced to 6 while the count beside it reported the true total, .dagCanvas reserving lane padding with a literal 9px while TS placed lanes at lane * EDGE_LANE_HEIGHT, and — the one that was actually measured — a ≤480px layer-skipping lane running its vertical segment through the step in between, at 12 lane/step overlaps on a 4-layer plan on the previous head. This is not a hardening PR looking for a reproduction.

Direction: aligned. It closes behaviour/shape items left open by the merged cockpit (#8583) plus the still-open #10866, entirely inside packages/web-shell/client. No auth, sandbox, model-selection, telemetry, release, or public-contract surface, so nothing to escalate.

Size: no core paths and no cross-package changes, so the two-tier core gate does not apply. Of the 2351 changed lines: 837 production (431 TSX, 406 CSS, plus 5 in i18n), 1298 test, 216 docs. Counting the design doc as neither test nor generated puts the non-test total at 1053, so the non-blocking 1000+ advisory technically trips — it is a big PR. Informational only; nothing here is blocked on size.

Approach: six distinct concerns ride together — node-face redesign, dependency navigation, the activity cap, the lane-pitch binding, the narrow viewport tiers, and the inspector chrome. In the abstract I'd have asked for these as separate PRs. Two things make me not ask now: several are genuinely entangled (dropping the id off the node face is what makes the step number necessary, which is what makes the chips navigable), and three rounds of @wenshao's runtime verification plus the autofix rounds have accumulated against this exact tree — splitting it would throw that away. Worth noting for the next round of this surface rather than acting on here.

Risk: no elevated risk signals — none of the 13 files match the revert-correlated path list, and the change is confined to one package's rendering layer.

Moving on to code review. 🔍

中文说明

本次是针对 head 132f2b27 的重跑。上一轮 gate 是 2026-09-03 针对 ff4b4907 做的,所以下面的结论基本都是新的——这是一次针对已大幅变动代码树的重新审查。

模板完整 ✓ —— 所有必需小标题都在,中文 <details> 也有。

问题: 已观测到的,不是理论性加固。每一项都能改前源码里直接指出来:依赖渲染成 todo.blockedBy!.join(', ')(两个面板里都是裸 id);活动列表截断到 6 条,而旁边的计数报的是真实总数;.dagCanvas 用字面量 9px 预留泳道内边距,而 TS 按 lane * EDGE_LANE_HEIGHT 放置泳道;以及唯一被真实测量过的那一项——≤480px 时跨层边的竖线穿过中间那个步骤,在上一个 head 的四层计划上有 12 处穿越。这不是一个在找复现的加固型 PR。

方向: 对齐。它补上已合并的 cockpit(#8583)遗留的行为/形态条目,加上仍未关闭的 #10866,改动全部落在 packages/web-shell/client 内。不涉及鉴权、沙箱、模型选择、telemetry、发布或公开契约,因此无需升级。

规模: 未触及核心路径,也没有跨包改动,所以两级核心 gate 不适用。2351 行改动中:生产代码 837 行(TSX 431、CSS 406、i18n 5 行)、测试 1298 行文档 216 行。把设计文档算作非测试非生成的话,非测试总计 1053 行,因此 1000+ 的大 PR 建议在字面上成立——这确实是个大 PR。仅供参考,不会因为规模而阻断。

方案: 六个独立议题被放在一起——节点面重设计、依赖导航、活动条数上限、泳道间距绑定、窄视口档位,以及 inspector 界面收敛。抽象地说我会要求拆成多个 PR。现在不提这个要求有两点原因:其中几项确实互相纠缠(把 id 从节点面拿掉,才使步骤号成为必需,才使 chip 可导航);而且 @wenshao 的三轮真实环境验证加上 autofix 的多轮迭代都是针对这棵确切的代码树积累的——拆开等于把这些验证作废。这一点更适合作为这个界面下一轮改动的提醒,而不是在这里执行。

风险: 无升级风险信号——13 个文件都没有命中与 revert 相关的路径列表,改动局限在单个包的渲染层。

进入代码审查 🔍

Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 132f2b275e56e2175155a59f48ac95ebc6349e5b · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Code review

I wrote my own proposal before opening the diff. For "make dependencies navigable" I'd have built an id → (index, title) map and rendered each reference as a control calling the existing selection handler, keeping the node-face chips read-only because they live inside the node's own <button>. For the activity cap, a local useState toggle. For the lane pitch, publish the constant from TS as a custom property and have the stylesheet read it — one source of truth, no second copy. For the narrow tiers, media queries narrowing a --plan-layer-width property plus the gap. For the chrome, delete the redundant surfaces rather than add new ones.

The PR does all five, and beats my proposal in four places I would not have thought of:

  • statesDependenciesVisibly turns "the dependency is stated exactly once" into an explicit invariant instead of an accident. Drawn edges are aria-hidden, so an interactive graph that hides the chip row would otherwise stop naming a node's blockers in the accessibility tree; the sr-only summary covers precisely that case and never doubles up with the visible row.
  • The ! attention mark. [data-attention='true'] re-declares the same token paused already wears, so without a shape channel a paused node needing attention is pixel-identical to a healthy one. Catching that from the stylesheet alone is good work.
  • border-left-color: var(--node-rule) after the border-color shorthand on selection — the shorthand expands to the left colour too, so without the re-declaration selecting a blocked node would read as a status change.
  • Source-level CSS guard tests for the specificity collisions (.activityList > button.showAllActivity, .dependencyList > li). jsdom computes no layout, so these are the only thing that can pin them; the comments explain the cascade arithmetic rather than just asserting strings.

What I verified against 132f2b275e rather than took on trust:

  • Every styles.* referenced in both TSX files resolves in its CSS module, in both directions — no undefined className, and no orphaned rule left behind by the node-face rewrite.
  • data-plan-input is gone from TSX, CSS and the test that queried it; no orphan consumer anywhere in packages/web-shell/client.
  • Step numbers really do agree across the three surfaces: the inspector list renders {index + 1} over todos.map((todo, index), and both new maps build from the same array and offset.
  • Both <small className={styles.metrics}> sites sit inside a <button> under .linkedAgents / .deliverables respectively, so the two new selectors actually match them.
  • The shoulder change is arithmetic-neutral where it claims to be: min(24, run/2) equals max(24, run/2) whenever run/2 ≥ 24, so wide gutters keep their exact curve. The Number.POSITIVE_INFINITY / NEGATIVE_INFINITY fallbacks for an unmeasured neighbouring layer collapse to min(24, ∞) = 24 — i.e. they degrade to the old fixed shoulder, never to a negative one.
  • Only EN and ZH locale objects exist, and both new keys were added to both.
  • formatRuntime / isSubAgentToolCall are imported, taskForTool / nestedTasksFromIndex / nestedAgentToolsForTool are in-file, and the new agent tally counts from the same two sources the rows render from.

Two things I looked hard at and am not reporting as findings, because both survive contact with the code:

  • Viewport @media for the narrow tiers, in a package that uses @container elsewhere. This stylesheet already has container-type: inline-size — but it is on .overviewContainer, deliberately scoped to the overview strip ("keeping the containment off .section lets the DAG's intrinsic width still size an auto-width dialog"), and .dagCanvas sits outside it. @container is therefore not available to the DAG without breaking dialog auto-sizing, and @media (max-width:) matches the convention used 25× across web-shell client CSS. The honest consequence — an embedded narrow panel on a wide viewport won't narrow — is the same limit the body already states.
  • .nodeId / .nodeStatus / .attention look like dead CSS after the node face dropped them. They aren't: the step-details panel still uses all three, which is consistent with "the id is an address, so it stays in the detail panel where it is copied."

Non-blocking findings

Nothing here blocks merge. All five are real, and four are already known to @wenshao's rounds — I'm recording them so they don't get lost when the branch is deleted.

  1. The description contradicts the diff on edge geometry. The body says "Edge routing, the layering algorithm, the return lanes and the measure pass are untouched", and Risk & Scope names only the adjacent-layer shoulder from 4860e0a7e6. But d91a0f7e74 rewrote the layer-spanning router — the return lanes: a new layerBounds map built inside the measure pass, dropX/riseX from a fixed 24px to min(24, run/2), and EDGE_CORNER replaced by a derived corner. All three "untouched" claims are false at head. This matters more than it sounds: the body is what someone reads when deciding whether a visual regression is revertable, and it currently understates the geometry change. A one-paragraph body edit fixes it.
  2. A stale number in a shipped comment. PlanExecutionView.module.css:247-250 says a 390px phone "scrolls the last ~6px". The measured overflow is 30px (396px of content against a 366px scroller); ~6px is the 414px figure. Verified still wrong at head. Related slip in the body only: "700px fits exactly (3x168 + 2x32 = 676 against 676)" — that sum is 568 (588 with padding, which is what the CSS comment correctly says); 676 is the scroller width.
  3. The >500-dependency chip row is uncapped. Ungating the row above MAX_RENDERED_PLAN_EDGES is the right call — it is the dependency's only statement there — but the node grows to 554px against 153px on base. A "first N + count" cap would keep the information without a half-screen node. Good follow-up.
  4. Focus falls to <body> after activating a dependency link or "Show all" (the latter unmounts itself on click, since it renders under !showAllActivity). In a PR whose thesis is navigation and accessibility, focus restoration is worth a follow-up issue. Not blocking: both controls stay operable and the pointer path is unaffected.
  5. Two mutants still survive — M24 (the lane test asserts each vertical is inside its gutter but not that it is centred, which is what the code comment claims) and M7. Test-guard tightening, already deferred as [Suggestion].
Files changed (13)
File What changed
packages/web-shell/client/components/messages/PlanExecutionView.tsx Node face rebuilt around step number + title + agent count/runtime; status moved to a left rule with an sr-only word; dependency chips gated on whether an edge or panel already states it; step-details references became controls; layer-spanning router derives shoulders from measured gutters; publishes the lane pitch as a CSS var
packages/web-shell/client/components/messages/PlanExecutionView.module.css Left status rule replaces per-status border tint; input port removed, output port de-accented; two narrow viewport tiers; lane padding reads the published pitch; new chip/link/meta/sr-only rules
packages/web-shell/client/components/messages/PlanExecutionView.test.tsx 11 new behaviour tests (arrowhead per gutter tier, lane crossing, dependency stated once, accessible name, agent tally incl. nested and dedup); geometry tests rebound to data-plan-node-id
packages/web-shell/client/components/messages/PlanExecutionView.css.test.ts Source-level guards pinning the specificity fixes so neither regresses to a bare class
packages/web-shell/client/components/workflow/SessionWorkflowInspector.tsx Upstream/downstream ids became numbered, clickable links with dedup and self-reference removal; activity preview cap got a show-all control; middle-dot metric strings became separate elements
packages/web-shell/client/components/workflow/SessionWorkflowInspector.module.css Section labels de-emphasised, count pill dropped, dependency chip list, show-all control with the .activityList > prefix, wrapping metrics row
packages/web-shell/client/components/workflow/SessionWorkflowInspector.test.tsx 4 new tests: link navigation both directions, dedup, self-reference drop, activity reachability past the cap
packages/web-shell/client/components/workflow/SessionWorkflowInspector.css.test.ts Guards for the show-all specificity, the metrics collision, and the li shrink floor
packages/web-shell/client/components/workflow/SessionWorkflowCockpit.tsx Fixed eyebrow moved onto the subtitle line so the selection accent stops being spent on a constant
packages/web-shell/client/components/workflow/SessionWorkflowCockpit.module.css Subtitle line becomes a wrapping flex row; workspace name and mono session ref get their own classes
packages/web-shell/client/e2e/visuals/session-workflow.spec.ts The 1m 14s assertion split per surface — it now renders on the node face too, so a page-wide locator would throw a strict-mode violation
packages/web-shell/client/i18n.tsx Two new keys (planExecution.agentCount, workflow.activity.showAll) in EN and ZH
docs/plans/2026-09-04-session-workflow-dag-design.md New design plan. English-only, which matches docs/plans/ — 0 of its 56 files carry a .zh-CN sibling, so the bilingual requirement for docs/design/ does not apply here

Testing

This is an unattended run, so I did not build or execute anything from this branch. The evidence below is the PR's own CI on the reviewed commit, read through the API, plus one thing I checked myself: the author says head 132f2b27 is content-identical to the d91a0f7e that @wenshao verified in round 3, and that is true — tree(d91a0f7e) == tree(132f2b27) == 1f65c11e, the only commit between them being a CI re-fire. So the three rounds of real-daemon Playwright measurement apply byte-for-byte to what CI just turned green.

Check Conclusion
Test (ubuntu-latest, Node 22.x) success
Test (macos-latest, Node 22.x) skipped
Test (windows-latest, Node 22.x) skipped
Lint & Static (ubuntu-latest, Node 22.x) success
Capture web-shell visuals (ubuntu-latest, Node 22.x) success
web-shell E2E Smoke (ubuntu-latest, Node 22.x) success
Integration Tests (no-AK, No Sandbox) success
Integration Tests (CLI, No Sandbox) skipped
Desktop Shell (ubuntu-22.04) success
Desktop Shell (windows-2022) success
Classify PR success
review-pr in_progress

No red checks, so there is no failing log to quote. The skipped entries are the workflow's own matrix decisions, not failures. review-pr still in flight is bot orchestration on the issue_comment event, not PR CI: counting only event == "pull_request" workflow runs on this SHA, both are completed/success, so there is nothing outstanding to wait for.

Not verified by this pass, and why it doesn't leave a hole: the ≤720px / ≤480px tiers, the inspector chrome, and the light theme have no rendered CI evidence — Capture web-shell visuals renders workflow-page-* only, and says so in the body. That gap was closed out-of-band by @wenshao's rounds 1–3 on a byte-identical tree (measured gutters, arrowhead end tangents per tier, 0 lane crossings on a 4-layer plan, status-rule contrast in both themes, Show all 8 runs on one line, chip rows on 25/25 leaves above the edge budget). If the head moves off 132f2b27, @qwen-code /tmux is the lane that would re-settle the narrow-tier and inspector rendering — CI cannot, since the visuals suite doesn't cover those surfaces.

中文说明

代码审查

我在看 diff 之前先写了自己的方案。要让依赖可导航,我会建一个 id → (序号, 标题) 的映射,把每个引用渲染成调用现有选中处理函数的控件,并保持节点面上的 chip 只读——因为它位于节点自己的 <button> 内部。活动条数上限用一个局部 useState 开关。泳道间距由 TS 以自定义属性下发、样式表读取——单一来源,不留第二份拷贝。窄视口档位用媒体查询收窄 --plan-layer-width 和间隙。界面收敛则删掉冗余的面,而不是加新元素。

这五点 PR 都做了,并且在四个我自己想不到的地方更好:

  • statesDependenciesVisibly 把"依赖只表述一次"变成显式约束而不是巧合。画出来的边是 aria-hidden,所以隐藏 chip 行的交互图否则就不再在无障碍树里说出节点的阻塞项;sr-only 摘要恰好覆盖这一情形,且绝不会与可见行重复。
  • ! 关注标记。 [data-attention='true'] 重新声明的正是 paused 已经穿着的那个 token,所以没有形状通道的话,一个需要关注的 paused 节点与一个健康节点在像素上完全相同。仅靠读样式表发现这一点,做得很好。
  • 选中时在 border-color 简写之后重新声明 border-left-color: var(--node-rule) —— 简写会一并展开到左侧颜色,少了这句,选中一个 blocked 节点看起来就像状态变了。
  • 针对特异度冲突的源码级 CSS 守卫测试.activityList > button.showAllActivity.dependencyList > li)。jsdom 不做布局计算,所以这是唯一能钉住它们的手段;注释解释了层叠算术,而不只是断言字符串。

以下是我对着 132f2b275e 亲自核对、而不是照单接受的:

  • 两个 TSX 文件里引用的每个 styles.* 都在各自的 CSS module 中有定义,双向核对——没有 undefined className,节点面重写也没留下孤立规则。
  • data-plan-input 已从 TSX、CSS 以及查询它的那个测试中一并移除;packages/web-shell/client 内没有遗留消费方。
  • 三个面的步骤号确实一致:inspector 列表在 todos.map((todo, index) 上渲染 {index + 1},两个新映射用的是同一个数组和同一个偏移。
  • 两处 <small className={styles.metrics}> 分别位于 .linkedAgents / .deliverables 下的 <button> 内,所以两条新选择器确实能命中。
  • 肩宽改动在它声称的地方是算术中性的:只要 run/2 ≥ 24min(24, run/2) 就等于 max(24, run/2),所以宽间隙保持完全相同的曲线。未测到相邻层时的 Number.POSITIVE_INFINITY / NEGATIVE_INFINITY 兜底会收敛为 min(24, ∞) = 24——也就是退回原来的固定肩宽,绝不会变成负值。
  • 只存在 EN 和 ZH 两个语言对象,两个新 key 都加进了两边。
  • formatRuntime / isSubAgentToolCall 已导入,taskForTool / nestedTasksFromIndex / nestedAgentToolsForTool 在同文件内定义,新的 agent 计数与行渲染取自相同的两个来源。

有两点我认真查过,但作为问题上报,因为它们在代码面前站不住:

  • 在一个别处使用 @container 的包里,窄屏档位用了视口 @media 这个样式表本身就有 container-type: inline-size——但它在 .overviewContainer 上,刻意只覆盖概览条("把 containment 从 .section 上拿掉,才能让 DAG 的固有宽度去撑开自适应宽度的对话框"),而 .dagCanvas 在它外面。因此 DAG 用不了 @container,除非破坏对话框的自适应尺寸;而 @media (max-width:) 与 web-shell 客户端 CSS 中 25 处的既有写法一致。诚实的后果——宽视口下嵌入的窄面板不会收窄——正是 PR 描述已经说明的那个上限。
  • 节点面去掉 .nodeId / .nodeStatus / .attention 后,它们看起来像死 CSS。 并不是:step-details 面板仍在用这三个,这与"id 是地址,所以留在你真正会去复制它的详情面板里"一致。

非阻断问题

以下都不阻断合并。五条都是真实存在的,其中四条 @wenshao 的历轮验证已经知道——我记录下来是为了分支删除后它们不会丢失。

  1. PR 描述在边几何上与 diff 矛盾。 描述说"边路由、分层算法、回程泳道和测量 pass 都没有改动",Risk & Scope 只提到 4860e0a7e6 的相邻层肩宽。但 d91a0f7e74 重写的正是跨层路由,也就是回程泳道:测量 pass 里新增了 layerBoundsdropX/riseX 从固定 24px 改成 min(24, run/2)EDGE_CORNER 换成推导出的 corner。这三处"未改动"在当前 head 上都不成立。这比听起来更要紧:描述正是别人判断某个视觉回归能否 revert 时会读的东西,而它现在低估了几何改动的范围。改一段描述即可。
  2. 已发布注释里的过时数字。 PlanExecutionView.module.css:247-250 写着 390px 手机"最后约 6px 需要滚动"。实测溢出是 30px(内容 396px 对滚动容器 366px);约 6px 是 414px 的数字。已核实在当前 head 上仍然是错的。描述里还有一处相关的笔误:"700px fits exactly (3x168 + 2x32 = 676 against 676)"——这个和是 568(加上内边距是 588,CSS 注释里写的正是这个正确值);676 是滚动容器宽度。
  3. 超过 500 条依赖时 chip 行没有上限。MAX_RENDERED_PLAN_EDGES 之上解除这一行的 gate 是对的——那里它是依赖的唯一表述——但节点会长到 554px,而 base 上是 153px。加一个"前 N 条 + 计数"的上限,既能保住信息又不会出现半个屏幕高的节点。适合做后续。
  4. 激活依赖链接或 "Show all" 后焦点掉到 <body>(后者渲染在 !showAllActivity 之下,点击时会自行卸载)。在一个以导航和无障碍为主旨的 PR 里,焦点归还值得开一个后续 issue。不阻断:两个控件仍然可操作,鼠标路径不受影响。
  5. 仍有两个变异体存活——M24(泳道测试断言每段竖线在间隙内,但没有断言它居中,而代码注释声称的正是居中)和 M7。属于测试守卫的加固,已作为 [Suggestion] 有意延期。

测试

这是无人值守的一轮,所以我没有构建或执行本分支的任何代码。下面的证据是通过 API 读到的、本 PR 自己在被审提交上的 CI 结果,外加我亲自核对的一件事:作者说 head 132f2b27@wenshao 第三轮验证过的 d91a0f7e 内容一致,这是真的——tree(d91a0f7e) == tree(132f2b27) == 1f65c11e,两者之间唯一的提交是一次 CI 重触发。所以那三轮真实 daemon 加 Playwright 的实测数据,逐字节适用于 CI 刚刚跑绿的这棵树。

没有红色 check,因此没有失败日志可引用。skipped 的几项是 workflow 自身的 matrix 决策,不是失败。仍在跑的 review-prissue_comment 事件上的 bot 编排任务,不是 PR CI:只统计该 SHA 上 event == "pull_request" 的 workflow run,两条都是 completed/success,没有悬而未决的东西需要等。

本轮未验证、以及为什么这不构成缺口:≤720px / ≤480px 档位、inspector 界面和浅色主题没有渲染类 CI 证据——Capture web-shell visuals 只渲染 workflow-page-*,PR 描述里也这么写了。这个缺口已由 @wenshao 第一至三轮在逐字节相同的代码树上补齐(实测间隙、各档位箭头末端切线、四层计划上 0 处泳道穿越、两个主题下状态竖条的对比度、Show all 8 runs 单行显示、超出边预算时 25/25 个叶子节点显示 chip)。如果 head 从 132f2b27 移开,@qwen-code /tmux 是能重新验证窄屏档位与 inspector 渲染的那条通道——CI 做不到,因为 visuals 套件不覆盖这些面。

Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 132f2b275e56e2175155a59f48ac95ebc6349e5b · re-run with @qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

Confidence: 4/5 — solid, and unusually well-evidenced for a rendering change; the reservations are documentation and deferred follow-ups, not the code.

Stepping back: this started life as a design-ledger cleanup on a merged feature and turned into something better than that. The part that convinced me isn't the visual work — I can read a stylesheet, and so can anyone — it's that the author kept finding the second channel. Status became a left rule, so status also had to stay in the accessibility tree as words. Attention folded into the same rule, which meant a paused node needing attention became pixel-identical to a healthy one, so it needed a shape mark. Edges are aria-hidden, so hiding the chip row on the interactive graph silently deleted the dependency from the node's accessible name — hence the sr-only summary and the statesDependenciesVisibly invariant that makes "stated exactly once" checkable instead of hoped-for. Each of those is a consequence of a deliberate simplification that the author then went and paid for. That is the opposite of the usual CSS PR.

My independent proposal was materially simpler in exactly one place — I'd have left the chip row gated and accepted the accessibility hole — and the diff is right and I was wrong. The lane-pitch binding is the cleanest single change here: a literal 9px in the stylesheet that had to be kept in sync by hand with EDGE_LANE_HEIGHT in TS, now published from the constant that actually places the lanes, with no var() fallback as a second copy. That's a defect class removed, not a defect fixed.

On the results matching the promise: I could not run this branch, and did not try. What I could do is check that the evidence attached to it is real rather than asserted — and it is. tree(d91a0f7e) == tree(132f2b27), so the maintainer's three rounds of real-daemon Playwright measurement (measured gutters, per-tier arrowhead tangents, 0 lane crossings on a purpose-built 4-layer plan where the previous head had 12, contrast ratios in both themes) apply to the exact commit CI just turned green. The two things round 2 called blocking are fixed and were re-measured; the M20 test gap is closed by a test whose name says what it pins. I verified the load-bearing static claims myself rather than trusting them: every CSS-module class resolves in both directions, data-plan-input has no orphan consumer, the step numbers genuinely agree across all three surfaces, both .metrics sites sit inside the <button> their selectors require, and the shoulder rewrite is arithmetic-neutral wherever it claims to be — including the infinity fallbacks, which degrade to the old fixed shoulder rather than to a negative one.

Two candidates I chased down and dropped, because reporting them would have been wrong: the narrow tiers use viewport @media in a package that uses @container elsewhere, but the only query container in this file is deliberately scoped to the overview strip so the DAG's intrinsic width can still size an auto-width dialog — @container simply isn't available to the canvas. And .nodeId / .nodeStatus / .attention look orphaned by the node-face rewrite but are still used by the step-details panel, which is where the body says the id belongs.

What I'd still want, and am not blocking on:

  • The description contradicts the diff. It says the return lanes and the measure pass are untouched; d91a0f7e74 rewrote the layer-spanning router and added layerBounds to the measure pass. This is the one item I'd genuinely like fixed before merge, because the body is what a future maintainer reads when deciding whether a visual regression is revertable — and it's a body edit, so it costs nothing and invalidates no verification. Also a stale "~6px" in the CSS comment where the measurement is 30px.
  • Four deferred follow-ups, all already known to the rounds: the uncapped >500-dependency chip row (554px node vs 153px on base), focus falling to <body> after activating a dependency link or "Show all", and the two surviving mutants (M24 lane centring, M7).

Per this repo's own rule about not letting review rounds balloon a PR, this one is far past five rounds — so those four belong in a follow-up issue, not in another round here. Only the description edit is worth doing now.

Housekeeping for whoever owns the autofix loop, not something I'll touch: the autofix/needs-human label is still on, and a human has now acted.

Verdict: approve. Both event == "pull_request" workflow runs on this SHA are completed and green, so there is nothing to defer to — approving now, pinned to the reviewed commit. @wenshao approved the same commit three minutes before this run; main wants two approvals, and this is the second. It also supersedes this bot's stale CHANGES_REQUESTED from acd5fd2 on 2026-09-06, which is the only thing still holding mergeStateStatus at BLOCKED — that review predates every fix in rounds 1–3.

中文说明

Confidence: 4/5 —— 扎实,而且对一个渲染类改动来说证据异常充分;我的保留意见都在文档和延期的后续项上,不在代码本身。

退一步看:这个 PR 起初是对一个已合并功能的设计台账清理,最后做成的东西比那更好。真正说服我的不是视觉部分——样式表谁都能读——而是作者一直在把第二条通道找出来。状态改成左侧竖条,于是状态也必须以文字留在无障碍树里。attention 并入同一根竖条,于是一个需要关注的 paused 节点与一个健康节点在像素上变得完全相同,所以它需要一个形状标记。边是 aria-hidden 的,所以在交互图上隐藏 chip 行,等于悄悄把依赖从节点的无障碍名称里删掉了——于是有了 sr-only 摘要,以及让"只表述一次"变成可检验而非靠期望的 statesDependenciesVisibly 约束。这几处都是一次有意简化所带来的后果,而作者随后一一把代价付掉了。这与常见的 CSS PR 正好相反。

我自己的方案只在一个地方明显更简单——我会让 chip 行保持 gated,并接受那个无障碍缺口——而 diff 是对的,我是错的。泳道间距绑定是这里最干净的一处改动:样式表里一个字面量 9px,必须靠人工与 TS 里的 EDGE_LANE_HEIGHT 保持同步,现在由真正放置泳道的那个常量下发,且不再以 var() 兜底的形式留第二份拷贝。那是消除了一类缺陷,而不是修好了一个缺陷。

关于结果是否兑现承诺:我不能运行这个分支,也没有尝试。我能做的是核实附在它上面的证据是真实的而不是声称的——而它是真实的。tree(d91a0f7e) == tree(132f2b27),所以维护者那三轮真实 daemon 加 Playwright 的实测(实测间隙、各档位箭头末端切线、在专门构造的四层计划上 0 处泳道穿越而上一个 head 有 12 处、两个主题下的对比度)适用于 CI 刚刚跑绿的那个确切提交。第二轮判定为阻断的两项已修复并重新测量;M20 的测试缺口由一个名称就说明它钉住什么的测试补上了。承重的静态结论我都是自己核对的,而不是照单接受:每个 CSS module 类在两个方向上都能解析、data-plan-input 没有遗留消费方、三个面的步骤号确实一致、两处 .metrics 都位于其选择器所要求的 <button> 内、肩宽重写在其声称的每一处都是算术中性的——包括那两个 infinity 兜底,它们退回原来的固定肩宽,而不是变成负值。

有两个候选问题我追下去之后放弃了,因为上报它们会是错的:窄屏档位在别处使用 @container 的包里用了视口 @media,但这个文件里唯一的 query container 是刻意只覆盖概览条的,好让 DAG 的固有宽度仍能撑开自适应宽度的对话框——canvas 根本用不上 @container。以及 .nodeId / .nodeStatus / .attention 看起来被节点面重写孤立了,但 step-details 面板仍在用它们,而那正是描述里说 id 应该待的地方。

我仍然希望、但不据此阻断的:

  • 描述与 diff 矛盾。 它说回程泳道和测量 pass 都没有改动;而 d91a0f7e74 重写了跨层路由,并在测量 pass 里加了 layerBounds。这是我唯一真心希望合并前改掉的一项,因为描述正是未来的维护者判断某个视觉回归能否 revert 时会读的东西——而且它只是改描述,不花什么代价,也不会让任何验证作废。另外还有 CSS 注释里那个过时的"约 6px",实测是 30px。
  • 四项延期的后续,历轮验证都已经知道:超过 500 条依赖时没有上限的 chip 行(节点 554px,base 上 153px)、激活依赖链接或 "Show all" 后焦点掉到 <body>、以及两个仍存活的变异体(M24 泳道居中、M7)。

按本仓库自己关于不要让评审轮次把 PR 撑大的规则,这个 PR 已经远超五轮——所以那四项应该进一个后续 issue,而不是在这里再开一轮。只有描述那一处值得现在改。

一件交给 autofix loop 负责人的杂务,我不会去动:autofix/needs-human 标签还挂着,而人已经介入过了。

结论:批准。 该 SHA 上两条 event == "pull_request" 的 workflow run 都已完成且为绿,所以没有需要延后等待的东西——现在批准,并绑定到被审提交。@wenshao 在本轮运行前三分钟批准了同一个提交;main 需要两个批准,这是第二个。它同时取代了本 bot 在 2026-09-06 针对 acd5fd2 留下的过时 CHANGES_REQUESTED——那正是唯一还把 mergeStateStatus 卡在 BLOCKED 的东西,而那次评审早于第一至三轮的每一个修复。

Qwen Code · qwen3.8-max-2026-09-02

Reviewed at 132f2b275e56e2175155a59f48ac95ebc6349e5b · re-run with @qwen-code /triage

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Needs some rethinking before this can merge — two blockers, both detailed in my Stage 2 note above.

  1. .showAllActivity is out-specified by the existing .activityList > button rule (0,1,1 vs 0,1,0), so the new "Show all N runs" control becomes a four-column grid and its label lands in the 52px first column. jsdom cannot see this and no visual spec renders the inspector, so it ships green. Same collision you already solved for .metrics.activityList > button.showAllActivity fixes it.
  2. The body says the raw-id dependency rendering was fixed "in both the graph node and the inspector detail", but PlanExecutionView.tsx:1311-1315 and :1352-1361 still join(', '). The node case is genuinely harder (that line sits inside the node's own button), so deferring is fine — the description just has to say so.

Also worth a look: 588px of DAG content still scrolls horizontally on a 390px phone, and the core/SDK test riders are not mentioned in the description.

中文说明

合入前需要再考虑一下 —— 两个拦截项,细节都写在上面的 Stage 2 评论里。

  1. .showAllActivity 被既有的 .activityList > button 规则压过(特异性 0,1,1 对 0,1,0),于是新的 "Show all N runs" 控件变成四列网格,标签落在 52px 的第一列里。jsdom 看不到这一点,也没有任何 visual spec 渲染 inspector,所以它会一路绿灯合进来。这正是你在 .metrics 上已经解决过的同一类冲突 —— 改成 .activityList > button.showAllActivity 即可。
  2. 正文说裸 id 的依赖渲染"在图节点和 inspector 详情两处"都修了,但 PlanExecutionView.tsx:1311-1315:1352-1361 仍然是 join(', ')。图节点那一处确实更难(那行位于节点自己的 button 内部),所以延后没问题 —— 只是描述里需要写明。

另外建议看一下:588px 的 DAG 内容在 390px 手机上仍然要横向滚动;以及 core / SDK 的两处搭载测试改动在描述里没有提到。

Qwen Code · qwen3.8-max-2026-09-02

Read against the three questions a plan graph is opened with — what is
running, what is blocked on what, what needs me — the node answered none of
them first.

**Reading order was inverted.** The node led with a status glyph, then the raw
Todo id, then the status word; `nodeContent`, the only element that says what
the step *is*, came third. At graph scale a reader scanned `● step-3 Blocked`
before "Compare findings". An id is an address and the least useful thing at
that scale, so it leaves the node face entirely — it stays in the detail panel,
where it is actually copied. Content leads, at 13px/500, behind the step
number the inspector list and dependency chips already use, so the three
surfaces address a step identically instead of three different ways.

**Status was stated three or four times per node** — glyph, status word,
border tint, plus an attention badge. It is now stated once, as a 3px left
rule, and only when it is worth saying: waiting is the resting state of most
nodes in a real plan and carries no rule. The stylesheet already made this
argument for `blocked`; extending it consistently is what stops the canvas
reading as a wall of tinted boxes and gives the one running node something to
stand out against. Attention folds into the same rule and outranks the status
tone, because it is the state the reader is being asked to act on.

Status is colour, which assistive tech cannot read, so it stays in the
accessibility tree as words.

**Time was invisible** in an execution trace. The node face now carries the
agent count and the summed agent runtime for that step — the "is this alive or
hung" signal, which is the first thing asked of a live run. Two facts with
real spacing, not one middle-dot-joined string that wraps as a unit and reads
out as a single run of text.

**Dependencies were stated twice**, as a drawn edge and again as
`Depends on step-3, step-7`. The row is now rendered only when no edge states
it — above `MAX_RENDERED_PLAN_EDGES` the graph draws none, and there the row
is the dependency's only statement, so deleting it outright would have lost
information in exactly the case that needs it most. It renders as the same
number-and-title chips the inspector uses.

The input port dot goes with them: the left edge now carries the status rule,
and an incoming edge already terminates in an arrowhead at the node — that
arrowhead is the input marker. The output port stays, since outgoing edges
leave their source unmarked. Both port dots also drop `--agent-blue-500`;
every node with an edge had one, so a permanently blue dot across most of the
graph left selection no colour of its own.

Edge routing, the layering algorithm, the return lanes and the measure pass
are untouched. This is the node face and the colour rule.

Three geometry tests identified a node by its first `<span>` — the Todo id.
That is now the accessible status word, so they bind to `data-plan-node-id`
instead, which is the stable hook and was always the better one.

Every behavioural change is mutation-checked: ungating the dependency row,
deleting the accessible status word, and dropping the elapsed each fail
exactly their own assertion.

Design plan, including the two revisions the review pass forced, is in
docs/plans/2026-09-04-session-workflow-dag-design.md.

Refs #10866.
@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 3, 2026

Copy link
Copy Markdown
Collaborator

🖼️ web-shell visual preview

Rendered against a mock daemon (no real backend): the PR base vs this PR head 132f2b2. Only screenshots that changed are shown (flows below, if any, are head-only) — refreshes on every push.

Screenshots · before / after

session-workflow-cockpit-dark before/after

session-workflow-cockpit-light before/after

Full-resolution recordings (.webm) are attached to the workflow run.

Qwen Code · web-shell visuals

ESLint flags the graph destructure binding as never used; the memo
still computes and returns the map for other consumers.

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
Patrol-Run: qwen-pr-conflict/jmtlr9x6ugb

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

Not reviewed: reverse audit — did not converge within the reverse-audit round cap of 5.

Test Plan (not a blocker): test/unit/daemonUi.test.tsno such file or directory.

[Critical] R1-3: [certifies-falsely] [regression] The description claims raw-id dependency rendering was fixed "in both the graph node and the inspector detail", but PlanExecutionView's own step-detail panel still renders selectedTodo.blockedBy!.join(', ') (~line 1402) and selectedDependents.join(', ') (~line 1407) — raw ids with no navigation — and the description does not name the deferral. The panel is reachable via TasksStatusMessage and ToolApproval, which render PlanExecutionView with showStepDetails defaulting to true: a user opening step details reads Depends on: research with zero clickable controls while the node face now shows "Completed 1 Research" (the id was removed from the face by this diff), so the panel's reference resolves to nothing visible — the exact gap this PR's "Dependencies become navigation" section claims to close persists on this surface. This is the exact blocker the prior CHANGES_REQUESTED review reported; verified still present at this HEAD. Witness: jsdom probe rendering PlanExecutionView (default showStepDetails) with a selected todo — panel text "Depends on: research" with 0 clickable controls in the row; the referenced node's face reads "Completed1Research". Fix: extend the same navigable chip/link treatment to the step-detail panel's Depends on / Unblocks rows, or state the deferral explicitly in the PR description as the prior review requested.

— qwen3.8-max via Qwen Code /review (v0.23.0)

Comment thread packages/web-shell/client/components/messages/PlanExecutionView.module.css Outdated
Comment thread packages/web-shell/client/components/workflow/SessionWorkflowInspector.module.css Outdated
Comment thread packages/web-shell/client/components/messages/PlanExecutionView.tsx Outdated
Comment thread packages/web-shell/client/components/messages/PlanExecutionView.tsx
Comment thread packages/sdk-typescript/test/unit/daemonUi.test.ts Outdated
Comment thread packages/web-shell/client/components/messages/PlanExecutionView.module.css Outdated
Comment thread packages/web-shell/client/components/messages/PlanExecutionView.css.test.ts Outdated
Comment thread docs/plans/2026-09-04-session-workflow-dag-design.md Outdated
yiliang114 added 3 commits September 4, 2026 11:21
…ontrol

Round-1 review findings.

**Blocker.** `.showAllActivity` was a single class (0,1,0), but the button is
a direct child of `.activityList`, whose base rule `.activityList > button`
(0,1,1) declares the four-column activity grid and wins. The label — a bare
text run that Grid wraps into an anonymous item — auto-placed into the 52px
first column and wrapped to three or four lines with three empty columns
beside it, and the intended padding lost too. Hover only looked right by
coincidence, because the row's own hover set the same background.

This is the collision handled two rules earlier for `.metrics`, missed here.
Both are now pinned by the stylesheet guard so neither can regress to a bare
class, and the fix resets `display` and `grid-template-columns` explicitly
rather than relying on source order.

**The graph's step-details panel still joined raw ids.** The node face's
chips cannot be controls — that row lives inside the node's own `<button>`
and a nested button is invalid HTML — but this panel sits outside it, so its
references become real controls that select the step they name, which is what
makes a dependency list navigation rather than text. `Depends on` and
`Unblocks` both.

**The narrow-viewport claim was wider than the fix.** Three 168px lanes plus
two 32px gutters is ~588px, which removes horizontal scrolling between there
and the 720px breakpoint but still scrolls on a 390px phone. Added a second
step below 480px that gets a three-layer plan under a phone viewport, and
said plainly in the comment that deeper plans still scroll — reusing the flat
layout stays with #10866. The guard test is renamed to what it pins: jsdom
computes no layout, so it can assert the two steps exist and narrow, not an
outcome.

**The lane pitch had a second copy.** The `var()` fallback restated `9px`
next to the `.dagCanvas` default, so "TS owns the pitch" held only when the
graph has dependencies. The default is now `0px` — the zero-lane case
collapses the reservation regardless — and the guard rejects the fallback
copy too.

**Comment antecedent.** "the prompt change" in todoWrite named nothing a
later reader could find; it now quotes the schema description it means and
says why a tool schema reaches every session.

Refs #10866.
…lipping its port

Two defects from my own review of the DAG pass, both in the class the round-1
review already caught once: invisible to jsdom, invisible to a passing unit
run, and only findable by reading the stylesheet.

**The status glyph was an accessibility channel, not decoration.** Making the
left rule the single carrier of status dropped the glyph — but the constant
it came from says exactly why it exists: "Status is carried by a glyph as well
as a colour so the graph survives colour-blindness, high-contrast mode, and a
greyscale screenshot." Colour plus a screen-reader-only word leaves a sighted
colour-blind reader, high-contrast mode, and any greyscale capture unable to
tell running from completed from needs-attention. "One status, one place" was
the wrong rule; the right one is one carrier of the status *colour*, plus a
redundant non-colour channel. The glyph returns for every status, in the meta
line so the step's content still leads, and muted so the rule stays the only
thing carrying the tone. Pinned per status, and pinned as distinct from each
other, so the shape channel cannot silently collapse.

**The status rule clipped the outgoing port.** Drawing it as `.node::before`
needed `overflow: hidden` for the node's 8px radius to apply to it — which
also clips the port dot at `right: -4px`, which is deliberately outside the
box. It is a `border-left` instead: border-radius clips it natively, no
overflow, and a transparent rule paints the node's own background so a resting
node reads as 3px of extra left padding with no width difference between
states. The stylesheet guard now rejects the overflow, the pseudo-element and
a missing port offset together, since the three only fail as a set.

Also drops `.nodeGlyph`'s dead sibling state: the rule is back in use, so
nothing here is unused CSS.

The design plan records both as corrections rather than folding them in, so a
later reader sees which of its rules were wrong and why.

Refs #10866.
Round-2 review findings that survive at this head; the round's three blockers
(the node's clipped port, the show-all specificity collision, and the
step-details panel's raw ids) were already fixed in the two commits after the
head it reviewed.

**The chips' `text-overflow: ellipsis` was dead.** It was declared on the
`inline-flex` chip itself, but `text-overflow` applies to a block container's
own inline content — the title sat in an anonymous flex item, so a long
dependency title was clipped hard with no ellipsis. The title now has its own
span carrying `overflow`, `min-width: 0` and the ellipsis, in all three places
that render a reference (the node face's read-only chips and both directions
of the step-details panel's links), and the stylesheet guard pins that it
lives on the title rather than the box.

**The comment above `.node` still described the old status behaviour** — it
said status drives the accent that drives the border, which this branch
stopped doing two commits ago. Rewritten to describe the border/rule split,
keeping the hazard it originally recorded: with status off the border, a
selected completed or blocked node can no longer pick up a second border in a
clashing hue.

**The compaction test's comment overclaimed.** It said every other keep-set
entry is pinned by that test; it pins `skills`. Narrowed to what it does.

Two coverage gaps the same round named:

- The inspector test exercised only the upstream direction. Upstream and
  downstream render through one helper but read from different sources
  (`blockedBy` versus the projection's `dependentsByTodo`), so covering one
  did not cover the other; the downstream link is now rendered, asserted and
  clicked.
- `planExecution.agentCount`'s singular and plural branches are separate
  formatter paths, and only the singular one was exercised — `1 agents` would
  have shipped green. Both are now pinned.

Refs #10866.
@yiliang114

Copy link
Copy Markdown
Collaborator Author

@qwen-code /review

Re-review requested against 2c7952b9. The two prior CHANGES_REQUESTED reviews both ran on earlier heads, and every blocker they raised is fixed since:

  • R1-1 — overflow: hidden on .node clipping the outgoing port. The status rule is a border-left now, which border-radius clips natively and needs no overflow. Found independently here before the review landed; fixed in 2313c065.
  • R1-2 — .showAllActivity (0,1,0) out-specified by .activityList > button (0,1,1). Fixed in edc3175e with the .activityList > prefix plus explicit display / grid-template-columns resets; both it and the earlier .metrics collision are pinned by the stylesheet guard.
  • R1-3 (Critical) — the step-details panel still joining raw ids. Both directions are navigable controls in edc3175e. The node face's chips stay read-only on purpose: that row is inside the node's own <button> and a nested button is invalid HTML.
  • R1-7 — removing the node glyph deleted the last non-colour status channel. Also found independently here; the glyph is back for every status, in the meta line so content still leads, muted so the rule stays the only carrier of the tone.

This head additionally closes R1-8 (the chips' text-overflow was declared on the inline-flex box, where it cannot fire — the truncation moved to the title span), R1-14 (the compaction comment overclaimed), R1-15 (the comment above .node still described the old status-on-border behaviour), and the R1-10 / R1-11 coverage gaps (the plural agentCount branch and the downstream dependency link).

One finding I believe is incorrect, flagged rather than silently skipped: R1-9 says .node[data-attention='true'] is declared before the .node[data-status=…] rules and therefore loses. It is declared after them — completed at line 324, running at 329, attention at 343 — and at equal specificity (0,2,0) the later rule wins, which is the intended precedence. Happy to be shown otherwise.

Also disclosed in the body: this environment has no browser, so the visual work was read from the stylesheet rather than rendered, and the Capture web-shell visuals suite has no scenario covering this surface — grep -rn "sessionWorkflow" packages/web-shell/client/e2e/ returns nothing. The two defects above are exactly what that gap costs. Adding a visuals scenario for the session workflow cockpit is worth doing and is not in this PR.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Qwen Code review request accepted. Review is running in workflow run. A command-triggered review is not listed under the checks of this PR; the result is posted here as a review when it finishes.

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Partially reviewed — gaps disclosed.

2 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

Not reviewed: reverse audit — ran to the 5-round cap without converging (round 5 still surfaced 2 findings, both verified before compose).

Deferred under the convergence posture (round 2, not a blocker) — recorded, not requested in this round:

  • packages/web-shell/client/components/messages/PlanExecutionView.tsx:1317 — [probe] D2-1 the either/or SR-only status word drops the status word for attention nodes
  • packages/web-shell/client/components/messages/PlanExecutionView.css.test.ts:36 — [probe] D2-2 the lane-pitch test pins TS publication of --plan-edge-lane-height but not of --plan-edge-lanes
  • packages/web-shell/client/components/messages/PlanExecutionView.css.test.ts:28 — [probe] D2-3 the lane-pitch guard never pins that the reservation multiplies pitch by lane count
中文说明

仅完成部分审查,审查缺口已披露。

本轮确认的 2 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

未审查(原文为英文):reverse audit — ran to the 5-round cap without converging (round 5 still surfaced 2 findings, both verified before compose).

收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 3 条(原文未翻译,列表见上方英文部分)。

— qwen3.8-max via Qwen Code /review (v0.23.0)

Comment thread packages/web-shell/client/components/messages/PlanExecutionView.tsx Outdated
Comment thread packages/web-shell/client/components/messages/PlanExecutionView.tsx
Comment thread packages/web-shell/client/components/messages/PlanExecutionView.tsx Outdated
Comment thread packages/web-shell/client/components/messages/PlanExecutionView.tsx
Comment thread packages/web-shell/client/components/messages/PlanExecutionView.test.tsx Outdated
Comment thread packages/web-shell/client/components/messages/PlanExecutionView.tsx

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewed. Suggestions are inline.

15 Suggestion-level finding(s) this review confirmed are already reported on this PR and are not repeated:

Deferred under the convergence posture (round 2, not a blocker) — recorded, not requested in this round:

  • packages/sdk-typescript/test/unit/daemonUi.test.ts:8558 — [test] the added skills keep-set assertion is inert with respect to this diff — it pins pre-existing SDK compaction behaviour and gates nothing this PR ships (deferred by the code-ag…
中文说明

已审查。 建议见行内评论。

本轮确认的 15 条建议级发现已在 PR 上报告过,不再重复发布(列表见上方英文部分)。

收敛姿态下延后(第 2 轮,非阻断)——已记录,本轮不要求修改:共 1 条(原文未翻译,列表见上方英文部分)。

— qwen3.8-max via Qwen Code /review (v0.23.0)

Comment thread packages/web-shell/client/components/messages/PlanExecutionView.tsx
Comment thread packages/web-shell/client/components/messages/PlanExecutionView.css.test.ts Outdated
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
@wenshao

wenshao commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Local runtime verification (round 3) — PR #10938 @ d91a0f7e

Recommendation: merge once CI has actually run green on this head. d91a0f7e74 meets both conditions from round 2, and this round found nothing new:

  • The lane crossing is gone. I checked the round-2 repro and a harder plan built for this round, where the layers a lane passes hold stacked steps and one edge skips two layers. At 700, 430 and 390px, every vertical lane segment now sits exactly on its gutter's centre line and crosses 0 steps. On the harder plan the previous head crossed 12. Desktop paths are byte-identical to the previous head.
  • The M20 test gap is closed. Removing the toolUseId dedup now fails the new test.

One optional tightening remains. The lane test checks that each vertical stays inside its gutter, but not that it is centred, so mutant M24 survives (section 3). CI has not started on this head (section 4), so there is no CI signal yet.

What changed since round 2

d91a0f7e74 touches 2 files:

  • PlanExecutionView.tsx (+62/−10). The router for layer-skipping edges now derives each shoulder from the gutter it crosses, min(24, run / 2), and halves the corner radius so the curve still fits: min(6, dropShoulder / 2, riseShoulder / 2). This is the round-2 candidate formula, with one difference: an unmeasured neighbouring layer falls back to the full 24px shoulder.
  • PlanExecutionView.test.tsx (+170). Two new tests. One checks lane geometry on the 64/32/18px gutters and pins the desktop path as a golden string. The other checks that an agent seen both as a live task and in subTools is counted once.

Environment

This round uses the same harness as rounds 1 and 2: Node 22.22.2 on Linux, and headless Chromium driven by Playwright 1.58.2. One real qwen serve daemon, built from 5c4f1de, backs every arm. That build is still valid, because every commit after 5c4f1de touches only web-shell client files: git diff --stat 5c4f1de..d91a0f7e shows 2 files, both under packages/web-shell/client. Each arm is a static proxy that serves its own bundle and forwards REST and SSE traffic to that daemon, so all arms render the same sessions.

Arm Port Bundle
base 4939 Sources from the merge base bc7a186
previous head 4943 5f70a13
lanefix 4941 Round-2 candidate patch. Not part of this PR
head 4944 d91a0f7e, from a fresh vite build

Both sessions were created through the real Plan & Review flow: enter_plan_modetodo_write with blockedByexit_plan_modeApprove and execute · Full Access.

  • DAG (from round 2): 5 steps in 3 layers. The edge 3 → 5 skips a layer, and the layer it passes holds a single step.
  • SKIP (new this round): 7 steps in 4 layers, with 3 steps in layer 1 and 2 steps in layer 2. Three edges skip layers: 1 → 5 and 3 → 7 skip one layer each, and 1 → 7 skips two. Every drop and rise gutter borders a layer with several steps. The DAG session cannot show this: round 2 noted that its drop segment missed a node only because the layer it passes has one step.

1. The round-2 repro (DAG session)

Viewport · gutter Previous head 5f70a13 Head d91a0f7e
1440 · 64px 0 crossings All 4 paths byte-identical to the previous head
700 · 32px Verticals at x=250 / 426 (gutters 222–254 / 422–454) x=238 / 438, the gutter centres. End tangent (6, 0)
430 · 18px The rise at x=251 passes through step 4 (106px of overlap) x=136 / 270, centred. 0 crossings, end tangent (2.5, 0)
390 · 18px The rise at x=246 passes through step 4 (106px of overlap) x=131 / 265, centred. 0 crossings, end tangent (2.5, 0)

At 700, 430 and 390px, all 4 paths are also byte-identical to the round-2 candidate. The adjacent-layer arrowheads keep the tangents the round-2 fix gave them: (28, 0), (12, 0) and (5, 0).

DAG session at 390px: previous head vs head

2. A harder plan: stacked layers and a two-layer skip (SKIP session)

Viewport · gutter base Previous head 5f70a13 Head d91a0f7e
1440 · 64px 0 crossings 0 crossings 0 crossings. All 11 paths byte-identical to the previous head and to the candidate
700 · 32px 0 crossings (the gutter is 64px at every width) 0 crossings, but all 6 verticals sit 12px off-centre, 4px from the next step All 6 verticals centred, end tangent (6, 0)
430 · 18px 0 crossings 12 lane/step overlaps. 1→5 drops and rises through steps 3 and 4, 3→7 through steps 5 and 6, and 1→7 through steps 3/4 and 5/6. Each vertical lands 10px or 106px inside a step, with up to 142px of overlap 0 overlaps. All verticals centred, end tangent (2.5, 0)
390 · 18px 0 crossings The same 12 overlaps 0 overlaps. Centred, end tangent (2.5, 0)

Live resize. I resized one page on the head from 1440 → 700 → 390 → 1440px. At every step, all 11 paths are byte-identical to a fresh load at that width, with 0 crossings.

SKIP session at 390px: previous head vs head

SKIP session at 700px: previous head vs head

Harness notes:

  • I discarded my first 1440px pass. The cockpit's graph viewport loads at different widths depending on inspector state (1132px or 628px), which moves every path by a constant offset. Each byte comparison above was made at the same viewport width. The narrow tiers are not affected.
  • Screenshots at ≤700px were taken with the inspector drawer closed. The auto-selected step opens the drawer over the graph, so I dismissed it with Escape and checked with elementFromPoint that the graph was on top. The geometry is read from the DOM either way.

3. Tests, mutation checks and static checks at d91a0f7e

Check Result
vitest run client/components/messages/PlanExecutionView client/components/workflow ✅ 9 files, 90 tests (88 at 5f70a13, plus the 2 new ones)
Full web-shell vitest run ✅ 311 files, 7694 tests
tsc -p packages/web-shell/tsconfig.json --noEmit ✅ 0 errors
eslint --max-warnings 0 on the 2 changed files · prettier --check on all 13 PR files ✅ · ✅

Each mutant below is a small, deliberate break in the new router (or, for M20, the agent count), run against PlanExecutionView.test.tsx; "killed" means the tests caught it. The source was restored byte-for-byte after each run.

Mutant Result
M22: both shoulders back to a fixed 24px (the round-2 defect) Killed: 18px drop column: expected 196 to be less than 186
M28: only the drop keeps 24px · M29: only the rise keeps 24px Killed · killed
M25: drop measured against its own layer · M26: rise measured against its own layer Killed · killed
M23: corner not halved · M27: corner back to the 6px constant Killed (end tangent x: expected 0 …) · killed (… -1 …)
M20: toolUseId dedup dropped (it survived in round 2) Killed by counts an agent seen as both a live task and a transcript sub-tool once
M24: run not halved (min(24, run)) Survives (43/43 pass)

M24 is minor but real. Without the halving, at 18px the vertical lands at nextLayer.left − 4, right on the tips of the adjacent edges' arrowheads. At 32px it lands 4px from the next step, which is the previous head's 700px geometry. The test only asserts "strictly inside the gutter", so nothing checks the centring the code comment claims. To kill M24, assert on the 32px and 18px tiers that dropX is (startX + leftOf(1) - 4) / 2, and mirror that for riseX. This is optional.

4. CI and review state

  • CI has not run on d91a0f7e. Qwen Code CI, Web-shell Visuals and the PR review workflow were created at 08:50 UTC and are still queued with 0 jobs. At the time of writing, 59 runs are queued across the repository while main's CI keeps completing, so this looks like an Actions backlog rather than anything in the PR. The previous head 5f70a13 finished all green: Test (ubuntu), Lint & Static, web-shell E2E Smoke, web-shell visuals, Integration (no-AK) and Desktop Shell (ubuntu and windows). d91a0f7e changes only PlanExecutionView.tsx and its test, and the local runs in section 3 cover both.
  • It merges cleanly with current main. main is at eb780062, 13 commits past the merge base, and none of those commits touch the PR's 13 files. On a local merge commit of the two (d91a0f7e + eb780062), the focused suites pass 90/90, full web-shell vitest passes 311 files and 7895 tests (the extra tests come from main), and tsc reports 0 errors.
  • 11 review threads are unresolved (9 not outdated). All of them are [Suggestion] test-guard hardening items that the author deferred on purpose: R2-5, R2-6, R4-1, R5-1 to R5-6, and the skills keep-set.
  • The PR is still marked CHANGES_REQUESTED. That status comes from qwen-code-ci-bot reviews on earlier heads. Rounds 1–3 fixed their Criticals and verified the fixes at runtime, so merging needs a fresh review or a dismissal.

Still open from earlier rounds

None of these block merge, and d91a0f7e does not touch any of them.

  • PlanExecutionView.module.css:247-250 still says a 390px phone "scrolls the last ~6px". The measured overflow is 30px.
  • The chip row for more than 500 dependencies has no cap. Not re-measured this round.
  • Focus falls to <body> after you activate a dependency link or "Show all" (D9-3). Not re-measured this round.
  • Mutant M7 still survives. Not re-run this round.

Not covered

  • macOS and Windows.
  • The light theme, for this round's captures.
  • The path through the Plan & tasks dialog.

Figures, JSON facts, logs and the probe scripts are in asserts/pr-10938/r3.

中文版

本地真实环境验证(第三轮)—— PR #10938 @ d91a0f7e

建议:等 CI 在这个 head 上真正跑绿后合并。 d91a0f7e74 满足了第二轮提出的两个合并条件,本轮没有发现新问题:

  • 泳道穿越已消除。 我检查了第二轮的复现会话,以及本轮专门构造的一个更难的计划:泳道经过的层里叠放了多个步骤,另有一条边跨两层。在 700、430、390px 下,每一段竖直泳道现在都正好落在所在间隙的中线上,与步骤的穿越为 0。上一个 head 在这个更难的计划上有 12 处。桌面端路径与上一个 head 逐字节相同。
  • M20 测试缺口已补上。 去掉 toolUseId 去重后,新测试会失败。

还剩一处可选的收紧。泳道测试只检查每条竖线落在间隙内,没有检查它是否居中,所以变异 M24 能存活(见第 3 节)。这个 head 上的 CI 还没开始跑(见第 4 节),目前没有 CI 信号。

与第二轮相比的变化

d91a0f7e74 改了 2 个文件:

  • PlanExecutionView.tsx(+62/−10)。 跨层边的路由现在按每一侧实际经过的间隙推导肩宽 min(24, run / 2),并把转角半径减半,让曲线仍然放得下:min(6, dropShoulder / 2, riseShoulder / 2)。这就是第二轮候选补丁的公式,只有一处差别:相邻层没有测量到时,回退为完整的 24px 肩宽。
  • PlanExecutionView.test.tsx(+170)。 新增两个测试。一个检查 64/32/18px 三档间隙下的泳道几何,并把桌面端路径钉成 golden 字符串。另一个检查:同时以实时任务和 subTools 条目出现的 agent 只计一次。

环境

本轮沿用前两轮的验证环境:Linux 上的 Node 22.22.2,浏览器是 Playwright 1.58.2 驱动的无头 Chromium。所有分支共用一个5c4f1de 构建的真实 qwen serve daemon。这个构建仍然有效,因为 5c4f1de 之后的每个提交都只改 web-shell 客户端文件:git diff --stat 5c4f1de..d91a0f7e 只列出 2 个文件,都在 packages/web-shell/client 下。每个分支是一个静态代理:提供自己的 bundle,把 REST 和 SSE 请求转发给这个 daemon,所以所有分支渲染的是同一批会话。

分支 端口 bundle
base 4939 merge base bc7a186 的源文件
上一个 head 4943 5f70a13
lanefix 4941 第二轮的候选补丁,不属于本 PR
head 4944 d91a0f7e,重新 vite build 所得

两个会话都通过真实的 Plan & Review 流程创建:enter_plan_mode → 带 blockedBytodo_writeexit_plan_modeApprove and execute · Full Access

  • DAG(沿用第二轮):5 个步骤分 3 层。边 3 → 5 跨过一层,它经过的那一层只有一个步骤。
  • SKIP(本轮新建):7 个步骤分 4 层,第 1 层 3 个步骤,第 2 层 2 个步骤。有三条跨层边:1 → 53 → 7 各跨一层,1 → 7 跨两层。每个下降间隙和上升间隙都紧挨着一个有多个步骤的层。DAG 会话展示不了这种情况:第二轮就指出,它的下降段没有碰到节点,只是因为经过的那一层恰好只有一个步骤。

1. 第二轮的复现(DAG 会话)

视口 · 间隙 上一个 head 5f70a13 head d91a0f7e
1440 · 64px 0 处穿越 4 条路径与上一个 head 逐字节相同
700 · 32px 竖线在 x=250 / 426(间隙 222–254 / 422–454) x=238 / 438,正好是间隙中线。 末端切线 (6, 0)
430 · 18px x=251 处的上升段穿过步骤 4(重叠 106px) x=136 / 270,居中。 0 处穿越,末端切线 (2.5, 0)
390 · 18px x=246 处的上升段穿过步骤 4(重叠 106px) x=131 / 265,居中。 0 处穿越,末端切线 (2.5, 0)

在 700、430、390px 下,这 4 条路径也与第二轮的候选补丁逐字节相同。相邻层边的箭头保持第二轮修复后的切线:(28, 0)、(12, 0)、(5, 0)。

390px 下的 DAG 会话:上一个 head vs head

2. 更难的计划:多步骤层与跨两层的边(SKIP 会话)

视口 · 间隙 base 上一个 head 5f70a13 head d91a0f7e
1440 · 64px 0 处穿越 0 处穿越 0 处穿越。11 条路径与上一个 head、候选补丁都逐字节相同
700 · 32px 0 处穿越(任何宽度下间隙都是 64px) 0 处穿越,但 6 条竖线都偏离中线 12px,离下一个步骤只有 4px 6 条竖线全部居中,末端切线 (6, 0)
430 · 18px 0 处穿越 泳道与步骤重叠 12 处。 1→5 的下降段和上升段穿过步骤 3 和 4,3→7 穿过步骤 5 和 6,1→7 穿过步骤 3/4 和 5/6。每条竖线落在步骤内部 10px 或 106px 处,重叠最高 142px 0 处重叠。竖线全部居中,末端切线 (2.5, 0)
390 · 18px 0 处穿越 同样的 12 处重叠 0 处重叠。居中,末端切线 (2.5, 0)

实时缩放。 我在 head 上把同一个页面从 1440 → 700 → 390 → 1440px 依次缩放。每一步的 11 条路径都与该宽度下刷新加载的结果逐字节相同,且都没有穿越。

390px 下的 SKIP 会话:上一个 head vs head

700px 下的 SKIP 会话:上一个 head vs head

验证环境说明:

  • 第一遍 1440px 的测量结果已作废。 cockpit 图区域的宽度取决于 inspector 的状态,每次加载可能是 1132px,也可能是 628px,这会让所有路径整体平移一个固定偏移。上面每次逐字节比对都是在相同视口宽度下做的,窄屏档位不受影响。
  • ≤700px 的截图是在关闭 inspector 抽屉后拍的。 自动选中的步骤会弹出抽屉盖住图,所以我先按 Escape 关掉,再用 elementFromPoint 确认图在最上层。几何数据是从 DOM 读取的,不受遮挡影响。

3. d91a0f7e 上的测试、变异与静态检查

检查 结果
vitest run client/components/messages/PlanExecutionView client/components/workflow ✅ 9 个文件、90 个用例5f70a13 上是 88 个,加上 2 个新用例)
web-shell 全量 vitest run ✅ 311 个文件、7694 个用例
tsc -p packages/web-shell/tsconfig.json --noEmit ✅ 0 个错误
对 2 个改动文件跑 eslint --max-warnings 0 · 对 PR 全部 13 个文件跑 prettier --check ✅ · ✅

下表每个变异体都是对新路由(M20 则是 agent 计数)做的一处小而刻意的破坏,再跑 PlanExecutionView.test.tsx;“被抓到”表示测试能发现这处破坏。每次跑完都把源文件逐字节还原。

变异 结果
M22:两侧肩宽都退回固定 24px(即第二轮的缺陷) 被抓到:18px drop column: expected 196 to be less than 186
M28:只有下降段保留 24px · M29:只有上升段保留 24px 被抓到 · 被抓到
M25:下降段按自身所在层测量 · M26:上升段按自身所在层测量 被抓到 · 被抓到
M23:转角不减半 · M27:转角退回 6px 常量 被抓到(end tangent x: expected 0 …)· 被抓到(… -1 …
M20:去掉 toolUseId 去重(第二轮存活) 被新测试 counts an agent seen as both a live task and a transcript sub-tool once 抓到
M24:跨度不减半(min(24, run) 存活(43/43 通过)

M24 影响很小,但确实存在。不减半时,18px 间隙下竖线落在 nextLayer.left − 4,正好压在相邻边箭头的尖端上;32px 间隙下竖线离下一个步骤只有 4px,也就是上一个 head 在 700px 下的几何。测试只断言“严格位于间隙内”,所以代码注释声称的居中没有被任何测试检查。要抓到 M24,可以在 32px 和 18px 两档上断言 dropX 等于 (startX + leftOf(1) - 4) / 2riseX 同理。这一项可选。

4. CI 与评审状态

  • d91a0f7e 上的 CI 还没跑。 Qwen Code CI、Web-shell Visuals 和 PR review 工作流都在 08:50 UTC 创建,至今仍是 queued,一个 job 都没有。撰写本报告时,整个仓库有 59 个 run 在排队,而 main 的 CI 一直在正常完成,所以这看起来是 Actions 积压,与 PR 本身无关。上一个 head 5f70a13 的 CI 全绿:Test (ubuntu)、Lint & Static、web-shell E2E Smoke、web-shell visuals、Integration (no-AK)、Desktop Shell(ubuntu 和 windows)。d91a0f7e 只改了 PlanExecutionView.tsx 及其测试,第 3 节的本地运行覆盖了这两个文件。
  • 与当前 main 可以无冲突合并。 main 现在是 eb780062,比 merge base 多 13 个提交,这些提交都没有改动本 PR 的 13 个文件。在本地把两者(d91a0f7e + eb780062)合成一个合并提交后:聚焦测试 90/90 通过,web-shell 全量 vitest 311 个文件、7895 个用例通过(多出的用例来自 main),tsc 0 个错误。
  • 还有 11 个评审 thread 未解决(其中 9 个未过期)。全部是作者有意推迟的 [Suggestion] 类测试守卫加固:R2-5、R2-6、R4-1、R5-1 至 R5-6,以及 skills keep-set。
  • PR 仍标记为 CHANGES_REQUESTED 这个状态来自 qwen-code-ci-bot 在更早 head 上的评审。那些 Critical 已在第 1–3 轮修复,并在运行时验证过,所以合并前需要一次新的评审,或者 dismiss 旧评审。

前几轮遗留项

这些都不阻断合并,d91a0f7e 也没有改动其中任何一项。

  • PlanExecutionView.module.css:247-250 的注释仍写着 390px 手机“最后约 6px 需要滚动”,实测溢出是 30px。
  • 依赖超过 500 条时,chip 行没有上限。本轮未重测。
  • 激活依赖链接或 “Show all” 后,焦点掉到 <body>(D9-3)。本轮未重测。
  • 变异 M7 仍然存活。本轮未重跑。

未覆盖

  • macOS 和 Windows。
  • 浅色主题(本轮的截图)。
  • 经由 Plan & tasks 对话框的路径。

图、JSON 数据、日志和探针脚本都在 asserts/pr-10938/r3

wenshao added a commit to wenshao/qwen-code that referenced this pull request Sep 13, 2026
The Qwen Code CI, Web-shell Visuals and review-lane runs created for d91a0f7 at 2026-09-13T08:50:29Z never scheduled a single job: a repo-wide Actions queue jam in that window left 59 runs stuck at status=queued with zero jobs, and both cancel and rerun are refused on them (cancel reports 'completed', rerun reports 'already running'). Runs created after ~09:30Z schedule normally, so these are dead rather than slow.

No content change. This empty commit only re-fires pull_request:synchronize so the lanes actually run on the exact tree wenshao validated in round 3 ('merge once CI has actually run green on this head').

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

Patrol-Run: qwen-pr-closeout/jmtzq8wyy28
@yiliang114

Copy link
Copy Markdown
Collaborator Author

CI 补跑说明:head 从 d91a0f7e 变成 132f2b275e内容零改动(空提交)。

@wenshao 你第三轮给的合并条件是「等 CI 在这个 head 上真的跑绿」。d91a0f7e 的三条 lane(Qwen Code CI / Web-shell Visuals / review lane)在 2026-09-13T08:50:29Z 创建后一个 job 都没排上:那段时间是仓库级 Actions 队列卡死,59 个 run 停在 status=queuedjobs=0cancel 回 "already completed"、rerun 回 "already running",两条路都被拒;而 09:30Z 之后创建的 run 都能正常调度,所以这三个 run 是死掉而不是慢。

因此推了一个空提交重新触发 pull_request: synchronize,让 lane 在你验证过的那棵树上真的跑起来。树与 d91a0f7e 完全一致,没有代码改动,也没有 rebase。

当前状态(head 132f2b275e):

  • 绿:Test (ubuntu-latest, Node 22.x)Lint & StaticIntegration Tests (no-AK, No Sandbox)Desktop Shell ×2、Capture web-shell visualsClassify PR
  • 还在跑:review-pr
  • mergeStateStatus 仍是 BLOCKED,原因是 ci-bot 的 CHANGES_REQUESTED 还挂在 09-06 的 8692beb7d0 上;review lane 在这个 head 出结论后会取代它,所以本轮没有再发 /triage(会和正在跑的 review lane 撞)。

11 条 unresolved 全是 [Suggestion],且都已带作者回复或 dev-bot 的延期说明,本轮没有动代码——你第三轮说「this round found nothing new」,再加改动只会把这次验证作废。

@wenshao

wenshao commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

@qwen-code /triage

@qwen-code-ci-bot

qwen-code-ci-bot commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator

Sandboxed verification: ✅ passed — merge-ready (agent verdict) - workflow run

Ran the PR in an isolated, token-free container: A/B against the base build, mock-free harness assertions, targeted gates. Advisory evidence for human reviewers — not a review, an approval, or a CI check.

Scripted assertions: 94 passed · 0 failed · 94 total

Flakiness gate: ✅ 4 changed test file(s) x 5 identical rounds, no divergence

中文 — 判定:✅ 通过 · 可合入(agent 判定)

沙箱验证在隔离、无凭证的容器中执行了该 PR 的代码(与 base 构建 A/B 对照、无 mock harness 断言、定向门禁)。仅作为评审证据,不构成评审、批准或 CI 检查

脚本断言:94 通过 · 0 失败 · 94 总计

抖动门:✅ 4 changed test file(s) x 5 identical rounds, no divergence

Verification report

PR #10938 — deep verification

Verdict: merge-ready — 94/94 scripted assertions passed, 0 failed. No blocking finding.

Verified head: 132f2b275e56e2175155a59f48ac95ebc6349e5b (git rev-parse HEAD^2)
Merge ref: 0507207f0d96594cf1a08436d95bbb2f48d76297 · Base control: 6a211b465227dd35ad3c35a2dcb86a809590bcd0 (HEAD^1)
Diff under test: 13 files, +2240 / −111, all under packages/web-shell/client plus one docs/plans/ document. No manifest, lockfile or tsconfig change, so the base arm reusing the root node_modules is a clean code-only control.

中文摘要

结论:merge-ready —— 94 条脚本化断言全部通过,0 条失败,无阻塞性发现。

A/B 结论:把同一份 harness(两棵树中 sha256 完全一致)分别跑在 head 与 HEAD^1 基线 worktree 上,19 条断言在 head 全绿、在 base 全红,19/19 均为 load-bearing——依赖导航、节点面改版、"依赖只表述一次"的三通道开关、泳道间距下发,每一项都由这次改动翻转。见下表与 01-ab-dependency-navigation-head-vs-base.png

边几何单独做了一组 A/B(02-edge-geometry-desktop-identical-narrow-tiers-fixed.png):桌面档 4 条边的 d 字符串与 base 逐字节相同,验证了 PR 声称的"桌面曲线不变";head 在三档断点上的末端切线 x 恒为正(28 / 12 / 5),而 base 在 ≤720px 恰好为 0、在 ≤480px 为 −14,正是箭头翻向的缺陷;跨层边在 base 的 ≤480px 档把竖直段画到了 dropX=150、而中间泳道左边界是 140,即侵入 10px,head 收回泳道内。32/32 断言通过。

变异矩阵(03-mutation-matrix-all-killed-no-survivors.png):9/9 变异体全部被杀死,0 存活,未变异基线 62/62 全绿,每次运行后源文件均已还原。

发现:均为 Suggestion 级,无阻塞项。最主要的一条是归因问题——节点面 chip 的去重与自引用过滤这两个守卫,只被一个为别的目的写的测试(skips SVG edge materialization for an excessively dense plan,断言 'Dense 0' 只出现一次)间接钉住,没有以它们命名的测试;对照之下 inspector 侧的同类守卫各有专属测试。

未覆盖范围:32 个提交中本地只可达 1 个(shallow checkout),无法做逐提交归因;容器内没有 Playwright 浏览器,改动过的 visuals spec 未能真实执行;所有几何数字来自按断点合成的 rect 喂给真实 router,不是浏览器排版结果,因此状态竖条的对比度(作者自己说"合并前不会省的验证")本轮未测;未跑仓库级 lint/build 与其他包的测试。

Central claim and A/B

Central claim. A step's dependency references stop being raw comma-joined Todo ids and become navigable controls labelled <step number> <title> that select the step they name — in the inspector detail (upstream and downstream) and in the graph's step-details panel.

Secondary claims. (A) The node face leads with number + title, carries agent count and summed runtime, keeps status in the accessibility tree as words, and states the dependency exactly once through one of two mutually exclusive channels. (B) The lane reservation is bound to the constant that places lanes, published from TS with no duplicated literal and no var() fallback.

One harness, byte-identical in both trees (sha256 dabcfafb…), run against the real components under jsdom with real React 19 rendering. It asserts the PR's claimed behaviour unconditionally, so the base arm is expected to go red — the driver scores a red base cell as a passing control assertion.

case assertion head base load-bearing
A1 inspector: each upstream dependency is its own control passed failed YES
A2 inspector: control labelled <number> <title>, never the raw id passed failed YES
A3 inspector: clicking an upstream dependency selects that step passed failed YES
A4 inspector: downstream (Unblocks) are numbered controls too passed failed YES
A5 inspector: dedups a repeated id and drops a self-block passed failed YES
A6 inspector: preview capped at 6, remainder reachable (6 → 8 rows, control retires) passed failed YES
B1 graph panel: each dependency rendered as a control passed failed YES
B2 graph panel: labelled <number> <title> passed failed YES
B3 graph panel: clicking navigates the panel to that step passed failed YES
B4 graph panel: Unblocks rendered as controls passed failed YES
C1 node face leads with number + title, raw id gone passed failed YES
C2 status kept in the accessibility tree as words passed failed YES
C3 agent count and summed runtime on the face (1 agent, 1m 14s) passed failed YES
C4 input port dropped, output port kept passed failed YES
D1 edges drawn → visible chips off, sr-only summary names blockers by number+title passed failed YES
D2 cockpit (showStepDetails={false}) → visible chips on, never both channels passed failed YES
D3 above MAX_RENDERED_PLAN_EDGES → 501 chips return, notice present passed failed YES
D4 chips keep a ghost id (? ghost), never a self-reference passed failed YES
E1 canvas publishes --plan-edge-lane-height: 9px beside --plan-edge-lanes passed failed YES

head 19/19 passed · base 0/19 passed · load-bearing 19/19 · 38 scored assertions, 0 fail.
Witness: 01-ab-dependency-navigation-head-vs-base.png. Raw: logs/ab-cells.txt, assertions-ab.json.

The base-arm reds are behavioural mismatches at the intended assertions, not import or compile failures — the harness rendered on both arms. Representative messages: expected [] to deeply equal [ 'build-api', 'build-ui' ] (A1), dependency control: expected null not to be null (A2), expected '--plan-edge-lanes: 0;' to contain '--plan-edge-lane-height: 9px' (E1). For A3 and B3 the base arm could not attempt the click at all because the control does not exist there; the preceding query in the same case establishes that absence.

Edge geometry A/B

The shoulder changes are the only computational change in the diff, and the PR body rests a safety argument on them ("the desktop curve and its pinned path … are unchanged"). The router reads real measured rects, so the harness feeds it the rects each breakpoint's CSS produces (lane 240/gutter 64, 168/32, 116/18, canvas padding 10 → 6px) and dumps the emitted d strings; the driver parses them and asserts the properties.

claim result
Desktop: all 4 edge paths byte-identical to base (incl. the spanning edge) PASS 4/4 — e.g. M 254 49 C 282 49, 282 119, 310 119 on both arms
Head: adjacent-layer end tangent x > 0 at every tier PASS — 28 (desktop), 12 (≤720px), 5 (≤480px)
Control base: adjacent end tangent x ≤ 0 at the narrow tiers PASS — exactly 0 at ≤720px, −14 at ≤480px (arrowhead flips)
Head: spanning drop and rise stay inside their own gutter PASS — dropX=131 in (122, 140) at ≤480px; corner halves to 2.5
Control base: spanning drop lands inside the intervening lane PASS — dropX=150 vs lane left 140, a 10px intrusion

32/32 assertions, 0 fail. The measured numbers reproduce the PR's own arithmetic exactly (a 24px run at ≤720px, a 10px run at ≤480px, min(24, 28) = 24 on desktop). Witness: 02-edge-geometry-desktop-identical-narrow-tiers-fixed.png. Raw: logs/geom-cells.txt, geom-ab.json.

Mutation matrix — are the PR's new tests load-bearing?

Each mutant reverts one guard in a scratch worktree at the merge commit and runs the PR's own changed suites (both .tsx files and both .css.test.ts guards, so the collection always contains the coverage).

mutant guard reverted result red assertion
baseline none green 62/62
M0 (control) delete the sr-only status word killed (2 red) groups executions by todo and keeps missing links unassigned
M1 inspector upstream: drop the repeated-id dedup killed (1 red) dedups a repeated dependency id in the upstream list
M2 inspector upstream: drop the self-reference filter killed (1 red) drops a self-reference from the upstream list
M3 node-face chips: drop dedup and self filter killed (1 red) skips SVG edge materialization for an excessively dense plan
M3a node-face chips: drop only the dedup killed (1 red) skips SVG edge materialization for an excessively dense plan
M3b node-face chips: drop only the self filter killed (1 red) skips SVG edge materialization for an excessively dense plan
M4 stop publishing --plan-edge-lane-height killed (1 red) stylesheet: takes the edge-lane pitch from TS instead of restating it
M5 restore the fixed 24px floor on the adjacent shoulder killed (1 red) keeps the arrowhead pointing at the target on every gutter tier32px gutter end tangent x: expected 0 to be greater than 0
M6 stop marking data-attention on the node killed (1 red) announces attention beside the status word, not instead of it

9/9 killed, 0 survivors. The M0 control is landed in the same collection as every mutant and turns tests red, so "no survivors" is a measurement, not an unexercised suite. Every run restored its file byte-for-byte (restored=true ×9) and the scratch tree was verified pristine before removal. M5's failure message is the same property the independent geometry A/B measured from rendered path strings — two instruments with different failure modes agree. Witness: 03-mutation-matrix-all-killed-no-survivors.png. Raw: logs/mutation-matrix.txt, mutation-matrix.json.

Lane pitch — single source of truth

Scored as scripted checks over the shipped files: the reservation reads calc(28px + var(--plan-edge-lanes) * var(--plan-edge-lane-height)); no * 9px literal survives anywhere in the stylesheet; var(--plan-edge-lane-height) carries no fallback, so there is no second copy of the constant; TS publishes it as `${EDGE_LANE_HEIGHT}px` and EDGE_LANE_HEIGHT = 9 is declared exactly once. 5/5 pass (part of assertions-score.json).

Step numbers agree across surfaces

The graph numbers steps from its own todos prop index, the inspector from its todos prop index, and the inspector's step list from todos.map((todo, index) => index + 1). Traced to the composition root: App.tsx feeds sessionWorkflowTodos both to SessionWorkflowCockpit (todos={sessionWorkflowTodos}, line 18835) and, through the artifact panel's workflow prop, to the inspector (todos: sessionWorkflowTodos, line 17468). Same array, same order, same rule — the "three surfaces address a step identically" claim holds. Verified by reading, not by a rendered side-by-side.

Corrections

These are corrections to the PR description's factual statements, not requests to change code.

  1. The affected-suite count is 90, not 84. At the verified head, vitest run components/messages/PlanExecutionView components/workflow collects 9 files / 90 tests, all passing. The body says "9 files, 84 tests" and explicitly disclaims it ("All counts above were measured before the main merge in 5c4f1de9e8; CI on the merged ref is the authority"), so this is the measurement the disclaimer points at rather than an inaccuracy. Note the filter also collects components/workflows/WorkflowRunsPage.test.tsx (14 tests), since components/workflow is a prefix of components/workflows — that is why the file count is 9.
  2. The snapshot's baseRefOid has drifted from the ref actually verified. $QWEN_VERIFY_CONTEXT reports baseRefOid: bc7a186cdadc…, which is not present in this depth-2 checkout. The merge ref was built against 6a211b46, and that is the control used throughout; the A/B therefore measures the diff as it will land.

Findings

No blocking findings. Two Suggestion-level observations, both about completeness of the test net rather than behaviour.

S1 — the node-face dedup and self-drop are pinned only incidentally

M3a and M3b were each killed by PlanExecutionView > skips SVG edge materialization for an excessively dense plan, a test written for a different purpose: it asserts a label appears exactly once on a dense node (split('Dense 0').length - 1 === 1), and a repeated or self-referencing blockedBy id makes it appear twice. So both guards are genuinely pinned today, but nothing is named for them, and the fixture that pins them is about edge suppression. If that fixture's dependency data is ever simplified, both guards silently lose their only pin.

The contrast is visible in the same matrix: the inspector's equivalent guards have dedicated tests (dedups a repeated dependency id in the upstream list, drops a self-reference from the upstream list), and M1/M2 each killed exactly their own. A pair of node-face cases mirroring those two names would close the gap. Per the project's review rules a missing test for changed behaviour is a Suggestion, not a Critical, and the behaviour itself is correct — my A/B cases D4 pins the same two properties from the outside (['1','Alpha'], ['?','ghost'], no self-chip).

S2 — the lane-pitch binding is asserted from source text, and the consumed padding is not asserted at all

M4 was killed by a stylesheet guard that matches the TSX source against /'--plan-edge-lane-height':\s*${EDGE_LANE_HEIGHT}px/. That is a real pin on the publish side, and the companion CSS guard pins the declaration side, but nothing asserts the property the pair exists for — that the canvas bottom padding actually equals lanes × pitch. Under jsdom that is unmeasurable (no layout), and the PR body already names the underlying limitation ("the lane test mocks offsetHeight"). This is a statement of where the net ends, not a defect: raising EDGE_LANE_HEIGHT now moves both the placement and the reservation, which is exactly the drift the change was made to prevent, and I verified the single-source property directly (5/5 checks above).

Scarier consequences tested, which do NOT hold

Reporting these because each was a plausible blocker and each is bounded evidence rather than reassurance.

  • The changed e2e visuals spec does not self-inflict a strict-mode violation. The PR puts 1m 14s on the node face and splits a page-wide getByText('1m 14s') into two surface-gated locators, which is the shape that usually leaves a second match behind. It does not. data-plan-node-id is on the node's <button>, not the wrapping <article>, so the node-scoped locator excludes the execution rows entirely; and those rows render their runtime only under {expanded && metrics.length > 0 …}, while the node's rows are rendered with renderExecution(tool)expanded = false. Each gated locator therefore resolves to exactly one smallest element. The inspector-scoped locator matches the single <span> in the linked-agent metrics row inside [data-testid="workflow-step-detail"], a testid that occurs exactly once in the client. This is a DOM-shape argument from the real components, not an executed Playwright run — no browser is installed in this container.
  • The unmeasured-layer fallback cannot re-open the narrow-tier defect. layerBounds.get(sourceLayer + 1)?.left ?? Number.POSITIVE_INFINITY makes an unmeasured neighbouring layer fall back to the full EDGE_SHOULDER (24px) — precisely the value that overruns an 18px gutter. I could not construct a configuration that reaches it: nodeRefs is populated by the ref callback for every rendered node, layerBounds is filled in the same loop as measuredNodes with no zero-size skip, and span > 1 guarantees the intermediate layer holds at least one todo. The fallback is defensive and correct as written; at narrow tiers it would be wrong, so it is worth knowing it is unreachable rather than load-bearing.
  • String(...)-style lossy coercion is not in play. Unknown dependency ids are handled by explicit fallbacks at each site (? + raw id on a chip, filtered out of the panel's selectedDependencies, filtered out of the inspector's upstream), and case D4 pins the ghost path end to end.
  • No locale gap. Both new message keys (planExecution.agentCount, workflow.activity.showAll) appear exactly twice in i18n.tsx — once in EN, once in ZH.
  • The English-only plan document is not a convention breach. The bilingual requirement in AGENTS.md and the review rules is stated for docs/design/, which carries 45 zh-CN counterparts; docs/plans/ has 57 files and zero zh-CN files, so this document follows its own directory's established convention.

Not covered

  • Per-commit attribution. The snapshot lists 32 commits; git rev-list HEAD^1..HEAD^2 reaches exactly one (132f2b27) because the checkout is depth 2 and git rev-parse --is-shallow-repository is true. I verified the aggregate HEAD^1..HEAD diff only. No per-commit table in this report was individually exercised, and none is presented.
  • The Playwright visuals suite. ~/.cache/ms-playwright does not exist in this container, so session-workflow.spec.ts (changed by this PR) and the workflow-page-* captures were never executed. Covered by the DOM-shape argument above, which is weaker.
  • Real browser layout, and the status rule's contrast. Every geometry number here comes from per-tier rects synthesized from the stylesheet's declared lane/gutter/padding values and fed through the real router. That reproduces the shape of each breakpoint, not a measured viewport, and it cannot answer the question the PR body flags as "the verification I would not skip" — the 3px status rule's contrast in dark mode. Also unmeasured: whether two meta facts actually crowd a 116px lane, and the .dependencyChip ellipsis behaviour, both of which need real layout.
  • The ≤720px / ≤480px media queries themselves. Verified as declared values in the stylesheet and as synthesized rects; never evaluated by a viewport.
  • Repo-wide gates. No root npm run lint, npm run format, npm run build, no packages/core / packages/cli tests, no integration or tmux suites. The diff touches only packages/web-shell/client and docs/plans/, so the web-shell workspace gate plus typecheck is the affected surface.
  • The Capture web-shell visuals CI job and its posted composites. No GitHub token in this job; nothing was fetched or compared.
  • No trial merge into current main. The merge ref already is a merge into the base tip 6a211b46, and main is not fetchable from here, so the "verify the merge, not only the PR" step could not be run against today's main.

Methodology

CI verify job, node:22-bookworm container, Node v22.23.2, npm 10.9.8, working tree at refs/pull/10938/merge (depth 2), npm ci + npm run build already complete at HEAD. Three harnesses, all mock-free with respect to the units under test: ab.test.tsx and geom.test.tsx render the real PlanExecutionView / SessionWorkflowInspector from source through vitest + jsdom with real React 19 createRoot/act and the real I18nProvider, and assert only on rendered DOM, attributes and emitted SVG path strings — the only interception is getBoundingClientRect/offsetWidth/offsetHeight in the geometry arm, which is how the router is fed a viewport it cannot otherwise have, and the existing suite's own technique. Class selectors are read from the CSS module each arm ships, because vitest hashes them per file; that makes the selector set arm-consistent by construction.

Control validity was asserted, not assumed. The base arm is a git worktree at HEAD^1 nested under the repo, so it resolves node_modules by walking up to the root install; the PR changes no manifest or lockfile, so that install is the right one for both arms. The units under test import @qwen-code/sdk/daemon type-only, which esbuild erases, so nothing crosses the workspace symlink (whose realpath does point into the head tree) at runtime. Independently, the base source was fingerprinted: data-plan-dependency occurs 0 times there versus 2 at head. Mutation runs used a second scratch worktree at the merge commit, verified git status --porcelain-clean before removal.

Drivers run-ab.mjs, run-geom.mjs and score-evidence.mjs in this directory own the comparison logic and the control expectation ("base goes red" is a passing assertion), so fail counts only unexpected outcomes. Harness sources are archived under harness/ and were removed from the working tree afterwards; git status --porcelain is empty. Raw per-arm logs are in logs/ (ab-cells.txt, geom-cells.txt, mutation-matrix.txt, score-evidence.txt, gate-affected-suites.txt, gate-typecheck.txt, web-shell-full-suite-junit.xml). Images were produced with node scripts/verify-capture.mjs.

Gates, reported separately and not double-counted in assertions.json: affected suites 9 files / 90 tests passed, exit 0; full web-shell workspace 313 files / 7915 tests passed, exit 0, with the archived junit agreeing (tests="7915" failures="0") — that run happened while this round's two harness files were still in the tree, so the PR's own surface is 311 files / 7895 tests; npm run typecheck (tsc -p tsconfig.json --noEmit) clean, exit 0. Gate liveness is evidenced by the mutation matrix: the same collection went red 9 times under single-guard reverts, so the green is a measurement rather than an unmatched glob.

assertions.json totals 94 = 38 (navigation A/B: 19 head-green + 19 base-red-as-expected) + 32 (geometry A/B, including 7 control cells) + 24 (mutation matrix scoring, restore integrity, and the lane-pitch single-source checks).

Flakiness gate log

e2e suite, out of gate scope: packages/web-shell/client/e2e/visuals/session-workflow.spec.ts
rounds=5 files=4 skipped=1
file packages/web-shell/client/components/messages/PlanExecutionView.css.test.ts: (cd packages/web-shell) npx --no-install vitest run ./client/components/messages/PlanExecutionView.css.test.ts
file packages/web-shell/client/components/messages/PlanExecutionView.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/messages/PlanExecutionView.test.tsx
file packages/web-shell/client/components/workflow/SessionWorkflowInspector.css.test.ts: (cd packages/web-shell) npx --no-install vitest run ./client/components/workflow/SessionWorkflowInspector.css.test.ts
file packages/web-shell/client/components/workflow/SessionWorkflowInspector.test.tsx: (cd packages/web-shell) npx --no-install vitest run ./client/components/workflow/SessionWorkflowInspector.test.tsx


per-file results (P=pass F=fail I=infra-exit, one letter per run):
  packages/web-shell/client/components/messages/PlanExecutionView.css.test.ts: PPPPP
  packages/web-shell/client/components/messages/PlanExecutionView.test.tsx: PPPPP
  packages/web-shell/client/components/workflow/SessionWorkflowInspector.css.test.ts: PPPPP
  packages/web-shell/client/components/workflow/SessionWorkflowInspector.test.tsx: PPPPP

verdict: pass
summary: 4 changed test file(s) x 5 identical rounds, no divergence

--- per-invocation detail (full copy in the artifact) ---
round 1 · packages/web-shell/client/components/messages/PlanExecutionView.css.test.ts: P (exit 0)
round 1 · packages/web-shell/client/components/messages/PlanExecutionView.test.tsx: P (exit 0)
round 1 · packages/web-shell/client/components/workflow/SessionWorkflowInspector.css.test.ts: P (exit 0)
round 1 · packages/web-shell/client/components/workflow/SessionWorkflowInspector.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/messages/PlanExecutionView.css.test.ts: P (exit 0)
round 2 · packages/web-shell/client/components/messages/PlanExecutionView.test.tsx: P (exit 0)
round 2 · packages/web-shell/client/components/workflow/SessionWorkflowInspector.css.test.ts: P (exit 0)
round 2 · packages/web-shell/client/components/workflow/SessionWorkflowInspector.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/messages/PlanExecutionView.css.test.ts: P (exit 0)
round 3 · packages/web-shell/client/components/messages/PlanExecutionView.test.tsx: P (exit 0)
round 3 · packages/web-shell/client/components/workflow/SessionWorkflowInspector.css.test.ts: P (exit 0)
round 3 · packages/web-shell/client/components/workflow/SessionWorkflowInspector.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/messages/PlanExecutionView.css.test.ts: P (exit 0)
round 4 · packages/web-shell/client/components/messages/PlanExecutionView.test.tsx: P (exit 0)
round 4 · packages/web-shell/client/components/workflow/SessionWorkflowInspector.css.test.ts: P (exit 0)
round 4 · packages/web-shell/client/components/workflow/SessionWorkflowInspector.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/messages/PlanExecutionView.css.test.ts: P (exit 0)
round 5 · packages/web-shell/client/components/messages/PlanExecutionView.test.tsx: P (exit 0)
round 5 · packages/web-shell/client/components/workflow/SessionWorkflowInspector.css.test.ts: P (exit 0)
round 5 · packages/web-shell/client/components/workflow/SessionWorkflowInspector.test.tsx: P (exit 0)

Evidence images

01-ab-dependency-navigation-head-vs-base

02-edge-geometry-desktop-identical-narrow-tiers-fixed

03-mutation-matrix-all-killed-no-survivors

Harness scripts and raw logs are in the workflow run artifacts (7-day retention).

Qwen Code · sandboxed verification

@qwen-code-ci-bot qwen-code-ci-bot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM, looks ready to ship. ✅

@yiliang114
yiliang114 added this pull request to the merge queue Sep 13, 2026
Merged via the queue into main with commit 01aa4f2 Sep 13, 2026
59 of 60 checks passed
@qwen-code-ci-bot

Copy link
Copy Markdown
Collaborator

Released in v0.23.4.

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

Labels

autofix/needs-human The autofix loop stopped on this PR — a human must re-arm, split, merge, or close it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants