Benchmark:Org Graph 三节点条点击可达详情
0. 文档定位与覆盖范围
本 Benchmark 对应 SDD「Org Graph 三节点条点击可达详情」,覆盖 frontend/app.js 中三处 Org Graph 渲染点里的两处(按 SDD 范围裁剪,第三处显式列为跟进项,见 §5):
renderOrgStrip()(v_dashboard,挂载于mountDashboard,id="org_strip")renderDetail()内联的 org-strip(v_task_detail,<h2>Org Graph</h2>面板)
本次不修改 renderOrgGraphTopology()(#/graph,v_graph_console)——它是独立实现(.graph-topo-node,非 .org-strip/.org-node),保留其当前"三节点不可点、仅内部 Work Graph 子节点可点"的行为不算回归,只要不引入新的不一致即可。
本任务在 docs/sdd/06-验收与测试锚点.md 的 REQ-01~15 体系之外——它是一个纯前端可用性/接线修复,不改变任何 M/C/P 层行为,不新增后端接口(沿用 GET /api/tasks/:task_id 已返回的 work_graph_runs[].{org_node,run_id,status,nodes} 与 artifacts[].{produced_by_run_id,...})。凡是新增后端路由、新增数据库字段、或复制/重写 DAG/日志/Artifact 三块已有渲染逻辑的实现,均视为不满足验收(违反 SDD"接线不重建"的硬约束)。
1. 前置事实与实现约束核对(每条 PASS/FAIL 都必须先过,再谈场景测试)
| # | 约束 | 核对方式 | 判据 |
|---|---|---|---|
| C1 | 未新增后端 HTTP 路由 | git diff 对比 src/backend_server.py 路由表 |
diff 为空或仅有注释改动 |
| C2 | 未新增/修改数据库 schema | git diff src/schema.py |
diff 为空 |
| C3 | DAG 展示复用 renderGraphVisualizerPanel |
读 frontend/app.js diff |
新代码调用 renderGraphVisualizerPanel(run, {...}),而非重写一份 DAG 渲染函数 |
| C4 | 日志展示复用既有 v_log_viewer 机制 |
读 diff | 新代码调用 renderLogViewerHtml/mountLogViewer(c_ws_log_tail 实时 + c_execution_log_get 静态兜底),未新建日志拉取逻辑 |
| C5 | Artifact 展示复用既有列表/v_artifact_browser |
读 diff | 新代码渲染/链接到既有 artifact 列表或 #/artifacts/:artifact_id,未重写 artifact 内容渲染 |
| C6 | "最新一次 work_graph_run" 用纯前端过滤实现 | 读 diff | 逻辑为 runs.filter(r => r.org_node === node).pop()(或等价实现),不新增查询参数 |
任一条 FAIL,直接判定本次交付未达标,退回,不进入下面的行为场景测试。
2. 路由与可点击性契约(Happy / Boundary / Failure)
契约:新增哈希路由 #/tasks/:task_id/org/:org_node,:org_node ∈ {planner, coder, validator}。点击任一 org-strip 节点应导航到(或等效展开)该组合视图;v_task_detail 现有渲染逻辑被复用,而非另起一套页面骨架。
2.1 v_dashboard 的 renderOrgStrip()
- happy:任务处于
coding态、assigned_org_node="coder",dashboard 渲染后,#org_strip内每个.org-node携带data-org-node="<planner|coder|validator>"且计算样式包含cursor:pointer(或对应 CSS class)。点击coder节点,window.location.hash变为#/tasks/<task_id>/org/coder。 - boundary:任务刚
queued,work_graph_runs为空(三个节点都还没跑过)。三个节点仍各自可点,点击后组合视图能正常渲染"暂无 Work Graph / 暂无日志 / 暂无 Artifact"空态,不抛异常、不出现空白页。 - failure:dashboard 尚未完成首次
refreshTasksAndOrgGraph()(orgStrip.innerHTML还是初始占位)时快速点击——点击处理器必须挂在渲染出的节点元素上(事件委托或渲染后绑定均可),不得因为绑定时机早于渲染而导致点击无响应;用"渲染完成后立即模拟 click 事件"验证监听器已生效。
2.2 v_task_detail 内联 org-strip
- happy:
#/tasks/<task_id>页面内<h2>Org Graph</h2>面板下的.org-node同样携带data-org-node,点击后在当前页面内滚动并高亮到该 org_node 对应的 Work Graph/日志/Artifact 区块(因为已经在v_task_detail里,不必再整页跳转),且 URL 同步更新为#/tasks/<task_id>/org/<org_node>(history.replaceState/hash 更新均可,只要地址栏可复制分享)。 - boundary:
activeSet(当前高亮的 org_node 集合,见renderDetail()现有逻辑)与被点击的 org_node 不一致时(例如任务已进入coder阶段,但用户点击已完成的planner),仍应正确定位到planner最新一次 run 的内容,不能只能查看"当前 active"的节点。 - failure:直接用带 org_node 的 URL 深链打开(例如浏览器地址栏直接输入
#/tasks/<task_id>/org/validator后回车,未经过点击),页面必须能独立完成初始渲染并定位到 validator 区块——即高亮/滚动逻辑不能只挂在 click handler 里,必须在mount阶段读取路由参数后统一触发一次("共享 scroll/highlight 逻辑"的核心要求)。
2.3 路由正则/派发(对齐现有 TASK_DETAIL_RE/ARTIFACT_DETAIL_RE 风格)
- happy:新增正则(例如
ORG_NODE_DETAIL_RE = /^#\/tasks\/([^/]+)\/org\/(planner|coder|validator)$/)能正确捕获task_id与org_node两个分组。 - boundary:
org_node段不在白名单内(例如#/tasks/abc/org/reviewer)不应匹配该正则,应落回render()现有的"Not found"兜底或原样按#/tasks/:task_id处理,不得让非法值穿透到后续的 DAG/日志/Artifact 过滤逻辑导致undefined报错。 - failure:
task_id段包含需要转义的字符(如 URL 编码过的 UUID 本身不含特殊字符,可用一个刻意构造的非法task_id如"' OR 1=1"编码后传入)时,前端不应据此拼出未转义的字符串插入 DOM(沿用现有escapeHtml),且fetch("/api/tasks/" + encodeURIComponent(taskId))对不存在的 task_id 应走 404 分支而不是让页面白屏。
3. 组合视图内容正确性(DAG + 日志 + Artifact 三块拼装)
契约:"最新一次相关 work_graph_run" = task.work_graph_runs.filter(r => r.org_node === orgNode) 取最后一条(数组按 started_at ASC 排序,因此 .pop()/取末项即最新)。
- happy(单次 run,已完成):
org_node="planner"只跑过一次且status="done"。组合视图应包含:(a) 该 run 的 DAG 面板(renderGraphVisualizerPanel产出的 HTML,包含run.run_id),(b) 该 run 下每个work_node_execution对应的日志入口,点开走静态拉取(c_execution_log_get,因为节点已终态),(c)produced_by_run_id === run.run_id的 artifact 列表项,可点击链接到#/artifacts/:artifact_id。 - happy(单次 run,进行中):
org_node="coder"当前status="running"。日志入口应走实时 WS tail模式(c_ws_log_tail),而不是静态一次性拉取——用 mock WebSocket 断言建立了连接且onmessage会追加内容到日志容器。 - boundary(多次 run / 重试循环):
org_node="planner"因full_replan被重新触发过,work_graph_runs中存在两条org_node="planner"的记录(不同run_id)。组合视图必须只展示最后一条(时间上最新)的 DAG/日志/Artifact,不能把两次 run 的节点混在同一个 DAG 里,也不能默认展示第一条。 - boundary(该 org_node 尚无 run):
org_node="validator"在work_graph_runs里完全没有记录(任务还在coding阶段)。组合视图应展示明确的空态提示(例如"validator 尚未开始"),DAG/日志/Artifact 三块均不渲染成"undefined"或抛异常。 - failure(该 org_node 有 run 但零 artifact 产出):
org_node="coder"有一条status="failed"的 run,且尚未产出任何 artifact(例如首次尝试就失败)。Artifact 区块应显示"暂无 Artifact"而不是空白或报错,DAG/日志区块仍应正常展示失败前的执行状态(work_node_execution.status里可能存在failed/reflecting节点,沿用renderGraphVisualizerPanel现有的 status class 展示,无需新逻辑)。
4. 生命周期/清理(复用现有 registerRouteCleanup/closeAllLogViewers 约定)
- happy:从组合视图(
#/tasks/<id>/org/coder,日志走实时 WS)导航离开到#/tasks,registerRouteCleanup注册的清理函数应被调用,对应的c_ws_log_tailWebSocket 连接被关闭(mock socket 的close()被调用一次)。 - boundary:同一页面内先点开
planner的日志(假设也是运行中态),再点coder(不离开v_task_detail页面)——旧的 WS 连接应关闭后再开新的,不允许同时保留两个未关闭的实时日志连接(沿用v_log_viewer现有的"懒加载 + 单例"约定,不新造一套)。 - failure:WS 连接建立后立即触发
hashchange(用户点击过快,两次路由切换之间日志面板还没渲染完)——不应抛出"Cannot read property of undefined"一类异常;用"渲染函数返回前先 mock 一次路由切换"来复现这个竞态并断言无未捕获异常。
5. 显式跟进项(本次不验收,但必须在交付说明中写明)
#/graph(v_graph_console)里renderOrgGraphTopology()渲染的三个拓扑节点(.graph-topo-node)仍然不可点——这是已知、有意保留的范围外项,Benchmark 不对其设置 PASS 条件,但要求交付说明里显式列出"未处理",不能被误读为"顺带修好了"或被静默忽略。若实现顺手把它也改了,允许,但不能因为改了这一处而让 §1 的 C3/C4/C5 复用契约破功(例如给拓扑节点造了第 4 套渲染逻辑)。
6. 自动化回归("留一个可运行检查")
沿用 frontend/test_graph_console_smoke.js 的既有约定(Node vm 沙箱跑 app.js,对生成的 HTML 字符串做断言,无框架无夹具),新增/扩展同风格的 smoke test(可以是新文件 frontend/test_org_click_through_smoke.js,或扩展现有文件——由实现者按现有文件职责边界判断),至少覆盖:
renderOrgStrip/renderDetail内联 org-strip 生成的 HTML 包含data-org-node属性(每个节点一个,值 ∈{planner, coder, validator})。- 新增的路由正则(
ORG_NODE_DETAIL_RE或等价物)能正确匹配/拒绝 §2.3 列出的 happy/boundary 用例(合法三态、非法节点名、缺段)。 - "取最新 run"的过滤函数:给定包含两条同
org_node记录的work_graph_runs数组,断言返回的是started_at更晚(或数组末位)的那条,而不是第一条。
判据:node frontend/test_org_click_through_smoke.js(或扩展后的既有文件)以 0 退出码结束,且新增断言数 ≥ 6(对应上面 3 类 × 至少 2 个用例)。
7. Definition of Done(本 Benchmark 的整体判据)
全部满足才算通过:
- §1 六条实现约束全部 PASS(未重建已有机制、未新增后端面)。
- §2 路由与可点击性的 happy/boundary/failure 用例全部可在浏览器手工验证(本仓库前端无构建步骤、依赖手工浏览器验证是既有约定)中复现预期行为。
- §3 组合视图内容正确性的 5 个场景(2 happy + 2 boundary + 1 failure)全部符合"最新 run 过滤 + 三块内容拼装正确"的判据。
- §4 生命周期清理的 3 个场景不出现 WS 连接泄漏或未捕获异常。
- §5 的范围外项在交付说明中被显式提及,未被静默默认为"已修复"。
- §6 自动化 smoke test 新增并以 0 退出码通过。
附:关键代码定位(供 Coder 节点核对,不代表本次要修改的完整清单)
frontend/app.js:589-598renderOrgStrip()(v_dashboard,待加data-org-node/点击)frontend/app.js:1085-1088renderDetail()内联 org-strip(v_task_detail,待加同样接线)frontend/app.js:855-renderGraphVisualizerPanel(run, options)(DAG 面板,{enableNodeClick:true}可选项已存在,直接复用)frontend/app.js:942-1016v_log_viewer(renderLogViewerHtml/mountLogViewer,WS 实时 + 静态兜底已验证生效)frontend/app.js:1571-v_artifact_browser(c_artifact_get,单 artifact 完整内容展示)frontend/app.js:97-99现有TASK_DETAIL_RE/SKILL_AUDIT_RE/ARTIFACT_DETAIL_RE路由正则风格,新增正则应沿用同一模式并加入render()的分派链(app.js:106-140)frontend/app.js:1322-1347renderOrgGraphTopology()(#/graph,本次范围外,仅供对照,不要复制其实现风格到 org-strip 的两处)src/task_query.py:115-171get_task()(已返回work_graph_runs/artifacts全量数据,本次不需要改动)