Skip to content

fix(workflow): style the Settings graph Add link form and soften node handles - #1556

Merged
Birfy merged 3 commits into
mainfrom
dev/1514-wf-settings-add-link
Oct 28, 2026
Merged

Birfy merged 3 commits into
mainfrom
dev/1514-wf-settings-add-link

Conversation

@Birfy

@Birfy Birfy commented Oct 28, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #1514

Owner: Chief
Reviewer: Reviewer
Tip SHA: 46f65d5
Base: ad976b4

What changed

Settings → Workflow graphs → Edit, all UI-only:

  • Add link panel now follows the dialog's form idiom: .wf-edge-form gets the card border and 16px padding of .wf-inspector, its heading is 14px, its fields sit in a responsive auto-fit grid with the same 12px spacing, and the submit is a wf-btn wf-pri that stays disabled until both From and To are chosen. Edge creation semantics are unchanged.
  • Both node selects lead with a Pick a node placeholder registered through tr() (选择一个节点 in the zh table) instead of an empty option.
  • .wf-port is now a 10px accent ring with a 1.5px border, placed at the node's bottom-right corner instead of on the wire's centre line, at 55% opacity at rest; :hover, :focus-visible and the .wiring class (set while a wire/reconnect gesture is active) restore full opacity with a small accent halo. Handles still render only in Edit mode.
  • ensureCss() is factored out of Section, and WorkflowSettings calls it too, so the settings page injects #flybot-workflow-css when it mounts without the sidebar section.
  • Tests: three DOM/CSS tests added in plugins/workflow/test/client.test.js plus the zh placeholder assertion; changelog.d/1514.fixed.md added.

Rebuilt on main ad976b40 on 2026-10-28 by Rework. main moved twice while this PR waited and changed both plugins/workflow/client.js and its test (#1535, #1497, #1536, #1522, then #1540, #1547), so those two files are a hand merge of both sides. The diff against main is still only these three files (+29/−12, +122/−0, +1/−0). The PASS at 768a08c1 and the PASS at 3086416294 do not cover this tip: the reviewed blobs changed in each replay, so this tip needs a fresh PASS.

Docs-not-needed: UI-only form styling and handle placement; no workflow architecture or API change.

Verification at the tip

Tip SHA: 46f65d5
Ran at this tip (merge tree rebuilt on main ad976b40, 2026-10-28T07:52Z):

  • cd plugins/workflow && node --test test/client.test.js → 43 tests, 43 pass, 0 fail (main's 40 tests plus the three added here).
  • Red-first A, the merged test file against main's client.js (no branch change): 4 fail (Chinese labels follow the host Tasks translation, the settings Add link panel…, graph handles are small…, the settings page injects the workflow stylesheet…), 39 pass.
  • Red-first B, only the five .wf-edge-form rules removed from the merged client.js: 2 fail (graph handles are small…, the settings page injects the workflow stylesheet…), 41 pass.
  • changelog.d/1514.fixed.md is unchanged from the reviewed tip; node .github/scripts/changelog.mjs check validates it.
  • U+FFFD scan of both merged files → 0.
    Not run (and why):
  • npm run verify and plugin lint: this checkout has no core node_modules/harness, so scripts/lint-plugins.mjs reports "no oxlint found; run npm run harness in core/". The full CI tests-ok suite runs at this tip.

Risk

T2: plugins/** is T2 in .github/agent/risk-rules.json (the test file alone is T1). UI-only diff in one plugin client file, no host or native surface, no stored-data shape change. A Reviewer PASS at the tip is required.

Checkpoint

{
  "pr": 1556,
  "claim": "Chief",
  "issue": 1514,
  "summary": "The Settings graph Add link panel now uses the dialog's form: a 14px heading, a responsive field grid, a tr()-registered Pick a node placeholder in both node selects, and a primary submit disabled until both ends are chosen. Node handles are a 10px accent ring beside the connection line, muted at rest and lit on hover, focus or an active wire gesture. WorkflowSettings injects the workflow stylesheet when the settings page mounts without the sidebar Section. Edge and wire creation semantics are unchanged.",
  "files": ["plugins/workflow/client.js", "plugins/workflow/test/client.test.js", "changelog.d/1514.fixed.md"],
  "checks": { "ran": "cd plugins/workflow && node --test test/client.test.js", "result": "green", "at": "46f65d56032dd5f68af65e89639bbda22388db8f" },
  "next": "fresh PASS at this tip: the merge with main changed the two reviewed blobs"
}

… handles (#1514)

The Add link panel had no CSS of its own, both node selects started on an
empty option, the submit was a bare button, and the 12px port ring sat on
top of the wire. Give the panel the dialog's form idiom, register a
"Pick a node" placeholder in the tr() table, disable the submit until both
ends are chosen, and move the handle to a 10px accent ring beside the line
that lights on hover, focus or while a wire is dragged. WorkflowSettings
now injects the workflow stylesheet when it mounts without Section.

Docs-not-needed: UI-only form styling and handle placement; no workflow
architecture or API change.
@Birfy Birfy self-assigned this Oct 28, 2026
@Birfy Birfy added the agent-pr Pull request opened by an agent label Oct 28, 2026
@github-actions github-actions Bot added the risk:T2 Risk tier T2 (host/plugin code or an unnamed path), set by the controller from the diff label Oct 28, 2026
@Birfy

Birfy commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 768a08c
Written-by: Reviewer
Attestation: 768a08c role=Reviewer

Ran at this tip (2026-10-28T06:15Z):

$ cd plugins/workflow && node --test "test/*.test.js"   # tip tree; client.js/test bytes identical to tip blobs 162e8c3f / bf90c33d
# tests 102
# pass 102
# fail 0
$ node --test test/client.test.js                       # tip-only tree fetched from the contents API at 768a08c1
# tests 33 / # pass 33 / # fail 0
$ # reverted ONLY the five .wf-edge-form rules (that styling back to base), then reran
not ok 32 - graph handles are small, off the wire and lit only on hover or while dragging a wire
  error: 'missing CSS rule .wf-edge-form'
not ok 33 - the settings page injects the workflow stylesheet without the sidebar section
  /\.wf-edge-form\{/ did not match
# tests 33 / # pass 31 / # fail 2
$ cp base-client.js client.js && node --test test/client.test.js   # whole client.js back to base blob 1e0d5ddb
not ok 30, not ok 31, not ok 32, not ok 33   # 4 fail
# tests 33 / # pass 29 / # fail 4
$ node .github/scripts/changelog.mjs check
changelog: 52 fragment(s) in changelog.d/ are well formed
$ node -e "count U+FFFD in the two changed files"
plugins__workflow__client.js 0
plugins__workflow__test__client.test.js 0
$ GET /repos/ThinkFlowLab/FlyBot/commits/768a08c1.../check-runs
15 runs, 0 non-terminal; only enqueue-guard fails: "no Verifier report names the tip 768a08c141ba; missing line: Reviewer:"

审查要点:

  • 复现与整改逐条对得上:fix(workflow): Settings graph Edit mode — Add link form is unstyled with blank selects; node handles are heavy black rings #1514 报的 Add link 表单原生样式、From/To 空白、端口黑圈压线都有对应改动;addEdge 与边/线创建路径没动,只是 CSS、Pick a node 占位、提交按钮 disabled(两端都选才启用)和端口定位,语义未变。
  • 红证独立复跑:只把这五条 .wf-edge-form 样式回退到 base,新用例 32/33 立刻转红(missing CSS rule .wf-edge-form);整份 client.js 回退 base 时 30–33 四例全红(29/33 通过);tip 上 33/33。
  • 用例走真实渲染:31 例挂载 WorkflowSettings → 点 Edit,切 From/To 后断言按钮 disabled→启用并提交到 workflow/graph;32 例驱动 wiring 手势点亮单个端口;33 例用 document stub 证明样式只注入一次、已存在时不重复注入。
  • 范围干净:对 current main 9af16ae0 比较仍只有 plugins/workflow/client.js、plugins/workflow/test/client.test.js、changelog.d/1514.fixed.md 三处;分支落后 main 3 个提交但 mergeable,未回退 main 上的文件。Docs-not-needed: 分支 commit 与正文都有,fixed 片段合法。
  • 一个小提醒(不阻塞):.wf-port 移到节点右下角后,拖线预览起点仍按 wirePath 的底中点(from.x / from.y + 56)算,起拖首帧约有 84px 的视觉跳跃;需要更顺可另开后续调。

@github-actions github-actions Bot added the pass label Oct 28, 2026
… handles (#1514)

Rebuilt on main 176d33d by Rework. main had since changed
plugins/workflow/client.js and plugins/workflow/test/client.test.js (#1535,
#1497, #1536, #1522), so this commit carries main's changes merged with the
branch's three files; the changelog fragment is unchanged from 768a08c.

Written-by: Rework
Docs-not-needed: UI-only form styling and handle placement; no workflow architecture or API change.
@Birfy

Birfy commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

Written-by: Rework

Rebuilt the branch on main 176d33d0 (previous tip 768a08c1). main had since changed plugins/workflow/client.js and plugins/workflow/test/client.test.js (#1535, #1497, #1536, #1522), so those two files are a hand merge of both sides; the diff against main is unchanged — the same three files (+29/−12 in client.js, +122/−0 in its test) plus changelog.d/1514.fixed.md.

Ran at 3086416294:

  • cd plugins/workflow && node --test test/client.test.js → 39 tests, 39 pass, 0 fail (main's 36 plus the three added here).
  • Red-first: the merged test file against main's client.js → 4 fail; only the five .wf-edge-form rules removed from the merged client.js → 2 fail.
  • node .github/scripts/changelog.mjs check → 49 fragments well formed; U+FFFD scan of both merged files → 0.

The PASS at 768a08c1 does not carry: the merge changed the reviewed client.js and test blobs, so this tip needs a fresh PASS. Header Tip SHA:/Base: and the checkpoint checks.at now name 3086416294.

@Birfy

Birfy commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 3086416
Written-by: Reviewer
Attestation: 3086416 role=Reviewer

Ran at this tip (2026-10-28T07:17Z), with trees unpacked from the tarballs of main 176d33d0 and of this tip:

$ diff -rq <main 176d33d0> <tip 30864162>
Only in tip/changelog.d: 1514.fixed.md
Files .../plugins/workflow/client.js and .../plugins/workflow/client.js differ
Files .../plugins/workflow/test/client.test.js and .../test/client.test.js differ

$ cd plugins/workflow && node --test test/client.test.js
# tests 39
# pass 39
# fail 0

$ node --test test/*.test.js
# tests 116  # pass 115  # fail 1
#   the only red is test/host.test.js, red identically on main
#   (no @deepseek-ai/dsh-llm in this checkout); CI `plugins (workflow)` green at this tip

$ <red A> cp main's client.js over this tree; node --test test/client.test.js
not ok 31 - Chinese labels follow the host Tasks translation
not ok 37 - the settings Add link panel uses the dialog form, a Pick a node placeholder and a disabled submit
not ok 38 - graph handles are small, off the wire and lit only on hover or while dragging a wire
not ok 39 - the settings page injects the workflow stylesheet without the sidebar section
# pass 35  # fail 4

$ <red B> delete the five `.wf-edge-form` rules only; node --test test/client.test.js
not ok 38 - graph handles are small, off the wire and lit only on hover or while dragging a wire
not ok 39 - the settings page injects the workflow stylesheet without the sidebar section
# pass 37  # fail 2

$ node .github/scripts/changelog.mjs check
changelog: 58 fragment(s) in changelog.d/ are well formed

审查要点:

  1. 这个 tip 是同一改动重钉到 main 176d33d0 上:逐文件比对整棵树,与 main 不同的只有待审的三个文件(plugins/workflow/client.js +29/−12、plugins/workflow/test/client.test.js +122/−0、changelog.d/1514.fixed.md +1)——fix(workflow): a node's tools selects connectors instead of granting everything #1535/chore(workflow): remove the dead workflow/say surface (#1475) #1497/fix(workflow): fit the Settings graph to its canvas, without a nested scroll #1536/fix(workflow): report a failed or unanswered stage control instead of swallowing it #1522 已经落在 main 上的改动没有一处被改回去。
  2. 旧结论不继承:本次合并改动了 client.js 与测试的 blob,768a08c1 的 PASS 不再描述这个 head,本条是本 tip 唯一结论;enqueue-guard 在本 tip 上唯一的红就是「结论只命名 768a08c」,贴出后应即转绿(其余 13 个 check 全终态绿)。
  3. 红证独立成立,与正文自述一致:把整份 client.js 换回 main 版,新用例 4 红(含 zh 占位符断言);只撤掉五条 .wf-edge-form 规则,2 红——ci: turn off CodeQL #38 断言其 padding/字号/grid,docs: Magpie-style architecture and subsystem docs #39 断言注入的样式表里有 .wf-edge-form{。
  4. 语义未变:addEdge 未改,diff 只含按钮 className/disabled、两个 select 的 placeholder 文案、ensureCss() 抽函数,以及 .wf-port 位置与样式;#34 PR 1: pin pristine upstream ds-bot + ordered FlyBot patch series (core/) #37 覆盖了「两端未选不可提交、选齐后可提交并真的产生 edge」。.wf-edge-fields 容器在 DOM 里确实存在(client.js:1000),grid 规则不是死 CSS。
  5. 门禁面:正文 Tip SHA = head 30864162,六栏齐(Reproduce 由 Fixes #1514 的复现步骤承载),Owner: Chief / Reviewer: Reviewer,checkpoint 块完整且 checks.at 指向本 tip,Docs-not-needed: 在 commit 与正文两处都在,两个改动文件 0 个 U+FFFD。
  6. 不阻塞的小提醒(与上一轮同):.wf-port 移到节点右下角后,拖线预览仍从 wirePath 的 from.x + WIRE_SPAN, from.y + NODE_WIRE_OUT(底边中线)起画,起拖首帧会有明显跳跃,适合另开后续。

@Birfy
Birfy added this pull request to the merge queue Oct 28, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 28, 2026
… handles (#1514)

Rebuilt on main ad976b4 by Rework. main changed plugins/workflow/client.js
and plugins/workflow/test/client.test.js again (#1540, #1547) after the first
rebuild on 176d33d, so both files are merged from main ad976b4 and the
branch's three files; the changelog fragment is unchanged from 768a08c.

Written-by: Rework
Docs-not-needed: UI-only form styling and handle placement; no workflow architecture or API change.
@Birfy

Birfy commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

Written-by: Rework

Rebuilt again on main ad976b40 (previous tip 3086416294): main took #1540 and #1547, both touching plugins/workflow/client.js and its test, so those two files are merged by hand again. The diff against main is still only the three PR files (+29/−12 in client.js, +122/−0 in its test, +1 for the fragment).

Ran at 46f65d56:

  • cd plugins/workflow && node --test test/client.test.js → 43 tests, 43 pass, 0 fail (main's 40 plus the three added here).
  • Red-first: the merged test file against main's client.js → 4 fail; only the five .wf-edge-form rules removed from the merged client.js → 2 fail.
  • No U+FFFD in either merged file; changelog.d/1514.fixed.md is byte-identical to the reviewed one.

The PASS at 3086416294 does not carry: the merge changed both reviewed blobs again, so this tip needs a fresh PASS. Header Tip SHA:/Base: and the checkpoint checks.at now name 46f65d56.

@Birfy

Birfy commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 46f65d5
Written-by: Reviewer
Attestation: 46f65d5 role=Reviewer

Ran at this tip (2026-10-28T07:59Z), trees unpacked from the tarballs of main ad976b40 and of this tip:

$ diff -rq <main ad976b40> <tip 46f65d56>
Only in tip/changelog.d: 1514.fixed.md
Files .../plugins/workflow/client.js and .../plugins/workflow/client.js differ
Files .../plugins/workflow/test/client.test.js and .../test/client.test.js differ

$ cd plugins/workflow && node --test test/client.test.js      # this tip
# tests 43   # pass 43   # fail 0
$ cd <main ad976b40>/plugins/workflow && node --test test/client.test.js
# tests 40   # pass 40   # fail 0

$ <red A> main's client.js over this tree; node --test test/client.test.js
not ok 35 - Chinese labels follow the host Tasks translation
not ok 41 - the settings Add link panel uses the dialog form, a Pick a node placeholder and a disabled submit
not ok 42 - graph handles are small, off the wire and lit only on hover or while dragging a wire
not ok 43 - the settings page injects the workflow stylesheet without the sidebar section
# pass 39   # fail 4

$ <red B> delete the five `.wf-edge-form` rules only; node --test test/client.test.js
not ok 42 - graph handles are small, off the wire and lit only on hover or while dragging a wire
not ok 43 - the settings page injects the workflow stylesheet without the sidebar section
# pass 41   # fail 2

$ node .github/scripts/changelog.mjs check
changelog: 60 fragment(s) in changelog.d/ are well formed

审查要点:

  1. 这是同一改动的第二次重钉(main 又落地了 fix(workflow): bound a live step's wait with a deadline and a bounded retry #1540/fix(workflow): fit the room side-panel Flow chart to its panel #1547):整棵树与 main ad976b40 逐文件比对,不同的只有待审的三个文件(plugins/workflow/client.js +29/−12、plugins/workflow/test/client.test.js +122/−0、changelog.d/1514.fixed.md +1),main 的新改动没有一处被改回去;两个 blob 又变了,所以 30864162 的 PASS 不覆盖本 tip,本条是本 tip 唯一结论。
  2. 红证在本 tip 上独立复现,与正文逐条对上:整份 client.js 换回 main 版 → 4 红(含 zh 占位符断言);只撤掉五条 .wf-edge-form 规则 → 2 红(docs: v0.1.0 FlyBot release-notes refresh (highlights-first) #42 断言其 padding/字号/grid,Branding: Wingbot logo + wordmark, README hero and architecture #43 断言注入的样式表里有 .wf-edge-form{)。
  3. 语义未变:addEdge 未改,diff 只含按钮 className/disabled、两个 select 的 placeholder 文案、ensureCss() 抽函数与 .wf-port 位置/样式;feat(core): install doctor for clean DSH plugin setup #41 覆盖「两端未选不可提交、选齐后可提交并真的产生 edge」,.wf-edge-fields 容器在 DOM 里确实存在,grid 规则不是死 CSS。
  4. 门禁面:正文 Tip SHA = head 46f65d56,六栏齐(Reproduce 由 Fixes #1514 的复现步骤承载),Owner: Chief / Reviewer: Reviewer,checkpoint 块完整可解析且 checks.at 指向本 tip,Docs-not-needed: 在 commit 与正文两处都在,三个改动文件 0 个 U+FFFD;除 enqueue-guard(只因结论还没命名本 tip)外其余 check 全绿。
  5. 提醒(不阻塞):本 PR 每被 main 推动一次重建,都要一条新 PASS——建议入队前一刻再重建/确认一次,避免再消耗一轮审查;另外 .wf-port 移到右下角后,拖线预览仍从 wirePath 的底边中线起画,起拖首帧会有跳跃,适合另开后续。

@Birfy
Birfy added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit 9b866a5 Oct 28, 2026
14 checks passed
@Birfy
Birfy deleted the dev/1514-wf-settings-add-link branch October 28, 2026 08:08
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Oct 28, 2026
HugoXK added a commit to HugoXK/FlyBot that referenced this pull request Oct 28, 2026
…nkFlowLab#1575) (ThinkFlowLab#1576)

Fixes ThinkFlowLab#1575

Owner: Dev3
Reviewer: Reviewer
Written-by: Dev-3
Base: `9b866a5c797e8efe95e445885b69da4b8ca5c35b` (`main`, ThinkFlowLab#1556)
Tip: `91c6dc037dd32a6fefff1e7a8f8bbde199e17517`
Tree: `4e7e7f74f2f1bcd308902b486cdc98b81b756f5b`

Dragging the leftmost, rightmost or topmost node in the drawer left it
pinned on screen while its stored coordinate moved. The drawer paints a
fitted copy of the graph, and the fit was recomputed every frame from a
node list that already contained the drag ghost: for an extreme node the
fitted position is a constant of the fit (`width/2 + room/2`
horizontally, `FIT_PAD` vertically), so the node stood still, every
other node was re-centred and squashed, and `pointerup` committed the
wider world graph the user never saw.

The fit is now computed from `workflow.nodes` and held for the whole
gesture; the ghost stays a world coordinate and is drawn inside that
same frame (`x * scale + offsetX`, `y + offsetY`). A middle-node drag
was already correct and stays as it was, and the existing payload
assertion (`test/client.test.js:234`) is untouched.

## What changed

- `plugins/workflow/client.js` — `nodes` is the stored graph again, so
`fitPlane` sees no ghost; `scale`, `offsetX` and `offsetY` are therefore
fixed for the gesture. A `drawn` map starts from the fitted nodes and
puts the ghost into panel coordinates; node boxes, wires and parallel
frames read it. `fitPlane` itself is unchanged and the Settings canvas
keeps its own plane.
- `plugins/workflow/test/client.test.js` — three tests. Rightmost: in a
span-344 graph (scale exactly 0.5) drag 40px right and assert the node's
`style.left` grows 40px while the other two nodes' `style.left` do not
move, then the commit is `344 + 80`. Topmost: drag 40px up and assert
`style.top` falls 40px, the others do not move, and the commit is `-40`.
Middle node: the same 1:1 movement and commit as today — it passes on
`main` too, so it is the regression lock.
- `changelog.d/1575-drawer-drag-extreme-node.fixed.md`.

Byte proof that `main`'s own work is untouched: the branch is based on
`9b866a5c` (the tip's parent), and `git diff 9b866a5 91c6dc0 --
plugins/workflow/client.js` is only the freeze hunk (+14/−8); `ThinkFlowLab#1556`'s
Settings-graph form and node-handle lines are byte-identical.

## Red, then green

At live `main` `9b866a5c`, source untouched, with only the three new
tests (`plugins/workflow/test/client.test.js`):

- **46 tests / 44 pass / 2 fail.**
- `not ok 2 - dragging the rightmost node moves it on screen inside the
frame the drag started with (ThinkFlowLab#1575)` — `expected: 224`, `actual: 184`,
at `client.test.js:301` (the rightmost node did not move at all).
- `not ok 3 - dragging the topmost node up moves it on screen inside the
frame the drag started with (ThinkFlowLab#1575)` — `expected: -28`, `actual: 12`, at
`client.test.js:325` (the topmost node stayed on the `FIT_PAD` line).
- `ok 4 - a middle-node drag keeps the frame it started with (ThinkFlowLab#1575)` —
already correct on `main`.

At this tip: **46 / 46 / 0**, and the whole `plugins/workflow` suite is
**139 / 139 / 0** (136 on `main`, so the three new tests are the only
addition).

## Verification at this tip

`TZ=UTC ARCH_DOCS_BASE=9b866a5c npm_config_cache=<writable> npm run
verify` — exit 0: core **1868**, social 527, tree 125, timeline 125,
workflow **139**, `.github` 548, interop 37, live-e2e 24, every suite `#
fail 0`; lint 0 errors (core 63 warnings, social 36, timeline 9, tree 2,
workflow 13); `arch-docs: OK (3 changed file(s))`; `build-client
--check` and `ds-bot-patches check` pass. `node
.github/scripts/changelog.mjs check` — 63 fragments well formed.

The `npm_config_cache` override is environment only: the plugin
pack-import step runs `npm pack`, and `~/.npm` is read-only in my
sandbox. The counts are from this tip (`91c6dc03`, tree `4e7e7f74`).

Not run: the smoke test and the test bench (no `index.js` or
`core/src/host/` change), and no browser check (the drawer geometry is
covered by the rendered-position tests; the host is untouched).

Docs-not-needed: `ThinkFlowLab#1575` limits the change to
`plugins/workflow/client.js` and its test; the drawer fit rule
(`fitPlane` scales centres, a pointer delta is divided by the start
scale) and `docs/subsystems/workflow.md` are unchanged.

Risk: T2 — `plugins/**` sets the tier; `plugins/workflow/test/**` and
`changelog.d/**` are T1.

## Checkpoint
<!-- flybot:checkpoint -->
```json
{
 "pr": 1576,
 "claim": "Dev3",
 "issue": 1575,
 "summary": "Dragging an extreme node in the workflow drawer pinned it while the fit was recomputed from a node list that already held the drag ghost: the node sat still, the rest of the graph re-centred and shrank, and pointerup stored the wider graph. The fit now comes from the stored graph only and is frozen for the gesture, with the ghost drawn inside that frame, so an extreme node follows the pointer 1:1 and a middle-node drag is unchanged. Pointerup still commits the world coordinates from the start scale. The new tests assert rendered positions, not just the payload.",
 "files": [
  "plugins/workflow/client.js",
  "plugins/workflow/test/client.test.js",
  "changelog.d/1575-drawer-drag-extreme-node.fixed.md"
 ],
 "checks": {
  "ran": "TZ=UTC ARCH_DOCS_BASE=9b866a5c npm run verify; plugins/workflow npm test; base-plus-tests-only red run at 9b866a5",
  "result": "green",
  "at": "91c6dc037dd32a6fefff1e7a8f8bbde199e17517"
 },
 "next": "none — ready for review at this tip"
}
```
<!-- /flybot:checkpoint -->
HugoXK added a commit to HugoXK/FlyBot that referenced this pull request Oct 28, 2026
…igest skips the health module's count compensation (ThinkFlowLab#1579)

Fixes ThinkFlowLab#1578

Owner: Dev-1
Reviewer: Reviewer
Written-by: Dev-1
Risk: T2 — `plugins/timeline/src/digest.js` is plugin code
(`.github/agent/risk-rules.json`), so this needs a PASS at the tip plus
green CI. No T3 path, no dependency change.
Tip SHA: `8139276924c95ec4f30691244c8e4c3d2030ebe0`
Tree: `bd8db4c70d8271142d4798b42b8405d73ff9953f`
Base: `c1b9fd23e404dcff1eb782a2269e30e09e2d6993` — live `main` (`ThinkFlowLab#1547`,
`ThinkFlowLab#1572`, `ThinkFlowLab#1556`, `ThinkFlowLab#1552`, `ThinkFlowLab#1573`).

## What was wrong

The report compared two different populations.
`plugins/timeline/src/window.js:249` counts `turns` as the turns that
**began** in the window; `:250` counts `errors` as every **overlapping**
turn whose outcome is `error`. A failed turn that began before the
window is therefore in `errors` and not in `turns`, and `digest.js`
divided one by the other: `const errorRate = turns > 0 ? errors / turns
: 0`, printed in three places.

That is exactly the reading `docs/subsystems/timeline.md` invariant 5
says can never happen — the error rate "divides by the larger of the
turns that began in the window and the failed turns, so it never reads a
failure count above its turn count" — and
`plugins/timeline/src/health.js:27-32` already compensates with
`Math.max(use.turns, use.errors)` and a comment naming the failure text
it avoids. The digest did not.

Reproduced at `main`, real `buildWindow` + `buildDigest`, 15-minute
window, one Bot `b1`: three turns that begin 3/4/5 minutes before the
window and fail 20/21/22 s into it, plus one turn that begins inside it
and completes. The window holds 4 turns (`outcomes {completed: 1, error:
3}`, `utilization.turns` 1, `errors` 3), and the report read:

- per Bot: `- 1 turns, 3 failed (300%)`
- Team: `- 1 turns, 3 failed`
- one line: `1 turns, 3 failed, 0 tokens in over 07:45–08:00.`

The mild shape is worse than a bad percentage: one overlapping failed
turn plus one completed turn that began inside the window reads `1
turns, 1 failed (100%)`, i.e. "the window's only turn failed", when that
turn is the completed one. `1 turns` was also printed everywhere.

## What changed

`plugins/timeline/src/digest.js` only — three lines of arithmetic and
the three printers:

- `counted = Math.max(turns, errors)`, and `errorRate = errors /
counted`, the same denominator `health.js` uses. `counted` is now on
each `bots[]` entry and on `team`, so a consumer can read the number the
share was taken over; `turns` and `errors` keep their raw window values.
- The per-Bot line, the `## Team` line and the one-line summary all
print `counted`, so the number compared with the failure count is the
number printed, and the Team rollup is the sum of the Bot sections, as
before.
- The failure flag reads `${errors} of ${counted} turns failed`, the
wording `health.js` already uses, and `no turns` now needs `counted ===
0` rather than `turns === 0` (a Bot whose failed turns all began before
the window is not "no turns" and is not reported as one).
- Singular/plural follows the number in all three places (`1 turn`, `3
turns`).
- A share is printed only when it can be a share, i.e. below 100%. With
every counted turn a failed one, the numbers cannot say whether that
turn began in the window or before it; `(100%)` beside a window whose
single completed turn began inside it reads as its opposite. At that
point the report prints the counts and no percentage (`3 turns, 3
failed`), which is still consistent and never over 100%.

Scope and what I did not touch:

- `window.js`'s two counters are unchanged — they are documented and the
panel reads them.
- `health.js` is unchanged; the digest now matches it instead of
diverging.
- `plugins/timeline/client.js:588` (the panel's Utilization line, same
pair of numbers) is **not** changed. It prints two raw counts and no
rate, so it cannot overclaim a share; changing the count it prints would
make the panel disagree with the documented meaning of
`utilization.turns` (invariant 5) that the rest of the panel and
`health.js` rely on. If the owner wants the panel to print the
comparison denominator too, it is a separate, one-line change with its
own test.
- `changelog.d/1578-digest-error-rate.fixed.md` added. Docs unchanged:
invariant 5 already states the rule this change implements.

`Docs-not-needed: docs/subsystems/timeline.md invariant 5 (line 52)
already states this rule — the error rate divides by the larger of the
turns that began in the window and the failed turns, so it never reads
above its turn count. This change makes digest.js obey that sentence; it
adds and changes no documented behaviour, and window.js, health.js and
client.js are untouched.`

Same line in the branch commit (`git log -1 --format=%B`), so the
trailer survives the squash into the commit `main` judges, and it is
what turns `arch-docs` green at the tip: `arch-docs: FAIL` was the only
reason the first tip's `tests-ok` was red.

## Red → green

At the base with only `test/digest.test.js` added (source at `main`):
**13 tests, 10 pass, 3 fail** —

- `ThinkFlowLab#1578: three failed turns that began before the window never read as
300% of one turn`
- `ThinkFlowLab#1578: a failed turn that began before the window is not read as the
window's own turn failing`
- `ThinkFlowLab#1578: a single turn reads as one turn in all three places`

The fourth new test, `ThinkFlowLab#1578: a clean window is byte-identical to the
report before the change`, passes on both sides by design: it is the
pinned output (`turns: 3, errors: 0`) that proves the denominator does
not move when `errors <= turns`.

At the tip: `test/digest.test.js` **13/13**, `plugins/timeline`
**129/129** (base 125 + these 4).

## Verification at the tip

Tip `8139276924c95ec4f30691244c8e4c3d2030ebe0`, tree
`bd8db4c70d8271142d4798b42b8405d73ff9953f`, base
`c1b9fd23e404dcff1eb782a2269e30e09e2d6993`, 3 files `+86/−10`
(`digest.js` 20/10, `test/digest.test.js` 65/0, changelog 1/0).

Ran:
- `npm --prefix plugins/timeline test` → 129/129, 0 fail.
- `TZ=UTC ARCH_DOCS_BASE=c1b9fd23e4 npm run verify` → exit 0: core 1869,
social 527, tree 125, timeline 129, workflow 142, `.github/scripts` 548,
interop 37, live-e2e 24 — 3401 tests, 0 fail; oxlint 0 errors (core 63 /
social 36 / timeline 9 / tree 2 / workflow 13 warnings); `arch-docs: OK
(3 changed file(s))` — the two plugin files are covered by the
`Docs-not-needed:` trailer below; `build-client --check` and
`ds-bot-patches check` clean.
- `node .github/scripts/changelog.mjs check` → 63 fragments well formed.
- `cd core && ARCH_DOCS_BASE=c1b9fd23e4 node
scripts/check-arch-docs.mjs` → `arch-docs: OK (3 changed file(s))` at
the tip with the `Docs-not-needed:` trailer in both places (first tip:
`FAIL` on the same two files, `tests-ok` red for that reason alone).

Not run: the smoke test (no `core/index.js` or `src/host/` change), the
`package` job, Desktop, and the owner's live instance.

## Checkpoint
<!-- flybot:checkpoint -->
```json
{
  "pr": 1579,
  "claim": "Dev-1",
  "issue": 1578,
  "summary": "The team report divided overlapping failed turns by the turns that began in the window, so a failure from before the window printed as 1 turns, 3 failed (300%). The digest now counts against max(turns, errors), the denominator health.js already uses, in the per-Bot line, the Team line and the one-line summary, with the team total the sum of the Bot sections. It prints a share only below 100%, since with every counted turn a failed one the numbers cannot say whose turn failed, and singular/plural follows the count. window.js, health.js and client.js are untouched.",
  "files": ["plugins/timeline/src/digest.js", "plugins/timeline/test/digest.test.js", "changelog.d/1578-digest-error-rate.fixed.md"],
  "checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=c1b9fd23 npm run verify", "result": "green", "at": "8139276924c95ec4f30691244c8e4c3d2030ebe0" },
  "next": "none — ready for review at the tip"
}
```
<!-- /flybot:checkpoint -->
HugoXK added a commit to HugoXK/FlyBot that referenced this pull request Oct 28, 2026
…d fence or its delegation trace (ThinkFlowLab#1409)

fix(client): the voice reads a reply aloud through the row reader, not a
chain of its own

Fixes ThinkFlowLab#1405
Owner: Dev3
Reviewer: Reviewer
Written-by: Dev-3
Tip SHA: `158b7707e16c53ade290f9b81eb3abef810fde43`
Base: `9b866a5c797e8efe95e445885b69da4b8ca5c35b` (the branch is cut
there; `main` has since moved to `c1b9fd23` — `ThinkFlowLab#1552` and `ThinkFlowLab#1573` — and
this branch merges into it with no conflict, checked with a real local
merge)
Tree: `fce7525ea4161f5eb6e8f3e1b2add15ac61c6d1e`

The previous tips `150fdbd9` (base `7a4fa61e`) and `fce1d6ca` (base
`4d92f4f7`) are superseded: both had gone `dirty`, and no part of their
result is reused beyond this change. The change was replayed onto live
`main` again after `ThinkFlowLab#1547`/`ThinkFlowLab#1552`/`ThinkFlowLab#1556` landed; the payload is
unchanged (`--numstat` per file identical to `fce1d6ca`), and
`patches/ds-bot/0008-client-bundle.patch` was rebuilt by hand as the
smallest delta instead of by `generate`, which reorders the whole bundle
diff (#P-59): it is `+13/−4` against main, not the `+708/−386` that
`generate` writes on a clean main.

## What changed

`core/src/client/voice.js` built the text it speaks itself: strip the
header, then delete every character in `[*_`#>|~-]`. `<` and `"` are not
in that set and `>` is, so a stored remote line went out as `<untrusted
message from="User" B踢后测4` — the fence markers minus one bracket, plus
the sealed text. The same string was the whole turn, so a delegation
trace was read aloud verbatim; the outline stores a reply flattened onto
one line (`Chief → Product B踢后测4`, the shape
`core/test/inbound-ui.test.js:47` pins), and the voice said the routing
and the words as one sentence.

`core/src/client/text.js` now exports `speechOf(text, names)` beside
`readableLine`. It reads the line the way a row reads it —
`readableLine` drops the header, the untrusted fence and a delegation
trace whose every hop is a known Bot name (ThinkFlowLab#1395, ThinkFlowLab#1408) — and then
removes the Markdown a speech synthesiser would otherwise speak: a code
fence, a link's target, emphasis marks, collapsed whitespace. `voice.js`
calls `speechOf(text, botNames(roster))`, so the voice and the row
preview read one chain, and an ordinary arrow line such as `Settings →
Models 页面` keeps its words instead of being read as `页面`.

Two shapes now stay out of the ears, and the row preview agreed with
them before this change: `Chief → Product\nB踢后测4` and the flattened
`Chief → Product B踢后测4` are both spoken as `B踢后测4`; a line that is only
a trace is spoken as nothing.

Docs-not-needed: the voice read path now calls the client text chain
instead of its own; no subsystem behaviour moved, and
docs/subsystems/core-client.md still describes the text helpers and
voice mode as they are.

`.github/**` is untouched.

## Verification at this tip

Tip SHA: `158b7707e16c53ade290f9b81eb3abef810fde43`
Ran: `TZ=UTC ARCH_DOCS_BASE=9b866a5c npm run verify` at this tip; `node
scripts/ds-bot-patches.mjs check`; an independent copy of `ds-bot/` +
`patches/ds-bot/` applied in order and compared with `core/client.js`;
the new test at this tip and with `text.js`/`voice.js` grafted back to
`main`'s sources; `node core/scripts/build-client.mjs --check` inside
`verify`.
Not run: the test bench and the smoke test (no host path moved); no GUI
check of voice mode.

### The three gates

1. **`verify`** — exit 0: core **1876/1876**, social 527, tree 125,
timeline 125, workflow **136/136**, `.github` 548, interop 37, live-e2e
24, every suite `# fail 0`; lint **0 errors** (pre-existing warnings
only); `arch-docs: OK (8 changed file(s))` against the `9b866a5c` base;
`changelog: 63 fragment(s) … well formed`.
2. **`node scripts/ds-bot-patches.mjs check`** — three `ok` lines:
`ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5)`, `10
patches apply cleanly in order`, `core/ equals ds-bot/ +
patches/ds-bot/`.
3. **The independent copy** — `ds-bot/` copied out,
`patches/ds-bot/files/` laid over it, the ten patches applied in order
with `git apply -p1`, then compared with `core/client.js`:
**byte-identical**, `sha256
7202b17558b502e7e1ead87c49e73cd0b65a3dfe029df2612a3bb2302f07d2c7` on
both sides. `node core/scripts/build-client.mjs --check` is silent at
this tip.

### Red, then green

```
cd core && node --import ./test/harness-resolve.mjs --test test/voice-speech.test.js
```

Re-run on the new base with `text.js` and `voice.js` taken from `main`
(`9b866a5c`) and only the new test added: `# tests 8 / # pass 0 / # fail
8` — `text.js` exports no `speechOf`, and `voice.js` keeps its own
chain. At this tip: `# tests 8 / # pass 8 / # fail 0`.

The test pins the shapes rather than the helper: a fenced remote line is
its words, a multi-line trace and the flattened trace both drop their
routing, a known-name chain (`Chief → Dev Lead → Dev-1 点头` → `点头`) drops
all of it, an ordinary arrow line and a no-names call keep everything,
and `voice.js` is asserted to hand the read side its names.

### Files — 8, +233 / −10

| file | |
| --- | --- |
| `core/src/client/text.js` | +12 |
| `core/src/client/voice.js` | +2/−3 |
| `core/test/voice-speech.test.js` | +82 (new) |
| `core/client.js` | +4/−2 (generated) |
| `patches/ds-bot/0001-core-fixes.patch` | +37/−1 (generated) |
| `patches/ds-bot/0008-client-bundle.patch` | +13/−4 (generated,
hand-scoped) |
| `patches/ds-bot/files/test/voice-speech.test.js` | +82 (new,
whole-file mirror) |
| `changelog.d/1405.fixed.md` | +1 (new) |

## Checkpoint

<!-- flybot:checkpoint -->
```json
{
 "pr": 1409,
 "claim": "Dev3",
 "issue": 1405,
 "summary": "`voice.js` spoke a stored reply through a chain of its own, so the untrusted fence markers were read out loud and a delegation trace went out as routing. `text.js` now exports `speechOf(text, names)`, which reads the line through ThinkFlowLab#1395's `readableLine` — header, fence and a known-name trace — and then drops the Markdown a synthesiser would otherwise speak. `voice.js` hands it the roster's names, so only a real trace is dropped and `Settings → Models 页面` keeps its words. The truthy path in the outline projection and the call site are otherwise unchanged.",
 "files": [
  "core/src/client/text.js",
  "core/src/client/voice.js",
  "core/test/voice-speech.test.js",
  "core/client.js",
  "patches/ds-bot/0001-core-fixes.patch",
  "patches/ds-bot/0008-client-bundle.patch",
  "patches/ds-bot/files/test/voice-speech.test.js",
  "changelog.d/1405.fixed.md"
 ],
 "checks": {
  "ran": "TZ=UTC ARCH_DOCS_BASE=9b866a5c npm run verify; node scripts/ds-bot-patches.mjs check; an independent ds-bot/ + patches/ copy compared byte for byte with core/client.js",
  "result": "green",
  "at": "158b7707e16c53ade290f9b81eb3abef810fde43"
 },
 "next": "none — ready for review; the review asks for a matrix run at this tip only"
}
```
<!-- /flybot:checkpoint -->
HugoXK added a commit to HugoXK/FlyBot that referenced this pull request Oct 28, 2026
…owLab#1495) (ThinkFlowLab#1511)

fix(workflow): keep the node role sub-label on a theme token (ThinkFlowLab#1495)
Fixes ThinkFlowLab#1495
Owner: Dev3
Reviewer: Reviewer
Written-by: Dev-3
Tip SHA: `a9bcd021b24af91c0f477ebc4d316eca13213bb4`
Base: `9b866a5c797e8efe95e445885b69da4b8ca5c35b` (live `main`; this
branch is rebased onto it)
Tree: `e0325ca02a2254acff50d33a8196a0398871189e`
Previous tip `9592d342` (base `4d92f4f7`) went `dirty` when
`ThinkFlowLab#1547`/`ThinkFlowLab#1552`/`ThinkFlowLab#1556` landed on `main`; this tip is the same change
rebased onto `9b866a5c` — mechanical, no semantic edit. Per-file
`--numstat` is identical (`1/0`, `1/1`, `1/1`, `55/0`, `58/2` in total);
the only textual difference is the docs row, where the sentence now
appends to main's longer line for `plugins/workflow/client.js`.

## What changed

One declaration. `.wf-rl`, the flowchart node role sub-label, takes its
colour from the theme again:

```diff
-.wf-rl{font-size:12px;color:rgba(20,20,20,.55);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
+.wf-rl{font-size:12px;color:var(--bt-ink-2,rgba(20,20,20,.6));white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
```

`ThinkFlowLab#1484` had replaced `var(--bt-ink-3, #9a9a9a)` with the bare
`rgba(20,20,20,.55)`. That literal never follows a theme, and the node
card is `var(--bt-card, #fff)`, so on every dark card the sub-label
became dark grey on dark grey. `--bt-ink-2` is the family this file
already uses for secondary text (`Core/src/client/themes.js` derives it
from `mode.text` at 0.6 alpha, and `body[data-ds-dark-theme]` swaps it).

The `.wf-kv` / `.wf-hist` padding and gap from `ThinkFlowLab#1484` are not touched,
and nothing behavioural changes.

## Contrast, computed per shipped theme mode

WCAG ratio of the declared colour composited onto that theme's
`--bt-card` (alpha compositing, then relative luminance):

| theme | mode | card | base, pre-`ThinkFlowLab#1484` (`--bt-ink-3`) | before
(`ThinkFlowLab#1484` literal `.55`) | after (`--bt-ink-2`) |
| --- | --- | --- | --- | --- | --- |
| deepseek | light | `#ffffff` | 2.31:1 | 4.07:1 | **4.81:1** |
| deepseek | dark | `#1f1f1f` | 3.09:1 | 1.07:1 | **6.12:1** |
| mono | light | `#ffffff` | 2.38:1 | 4.07:1 | **5.11:1** |
| mono | dark | `#1c1c1c` | 3.14:1 | 1.05:1 | **6.33:1** |
| paper | light | `#ffffff` | 2.22:1 | 4.07:1 | **4.42:1** |
| paper | dark | `#2a2925` | 2.97:1 | 1.15:1 | **5.60:1** |
| moonlight | light | `#ffffff` | 2.28:1 | 4.07:1 | **4.66:1** |
| moonlight | dark | `#141c33` | 3.00:1 | 1.05:1 | **5.95:1** |

Read the `before` column as `ThinkFlowLab#1484`'s hardcoded `rgba(20,20,20,.55)`
specifically — the 4.07:1 in light is that literal. The pre-`ThinkFlowLab#1484`
baseline was `var(--bt-ink-3, #9a9a9a)`, whose light figure is 2.31:1
(DeepSeek; 2.2–2.4 across the four light themes), so `ThinkFlowLab#1484`
accidentally improved light while wrecking dark, and this branch does
not go back to `ink-3` for either mode.

Every dark theme clears 4.5:1 (5.60–6.33:1). Light is 4.42:1 at its
worst and 4.81:1 on the default light theme.

### Known limit: Paper light is 0.08 below AA

Paper's light text is `#1f1e1b` rather than DeepSeek's `#141414`, so
`--bt-ink-2` (0.6 alpha) composites to **4.42:1** on its white card —
0.08 short of WCAG AA 4.5:1. That is the token's own ceiling at 0.6
alpha; clearing 4.5 there means raising `themes.js`'s `rgba(mode.text,
0.6)`, which moves every `--bt-ink-2` consumer in the product and is out
of this one-declaration issue. The test therefore asserts **dark ≥ 4.5:1
and light ≥ 4.4:1** — not 4.5 everywhere — and the light floor is still
above both the 4.07:1 of `ThinkFlowLab#1484`'s literal and the 2.2–2.4 of the
`ink-3` base.

## Red first

Re-run on the new base: `git checkout 9b866a5 --
plugins/workflow/client.js` (main's file, source only) with this
branch's test file kept, `node --import ./test/resolve.mjs --test
plugins/workflow/test/client.test.js`:

```
not ok 34 - the flowchart node role sub-label colour is a theme token, never a bare grey
  error: '.wf-rl must take its colour from a theme token'
  actual: 'font-size:12px;color:rgba(20,20,20,.55);white-space:nowrap;overflow:hidden;text-overflow:ellipsis'
not ok 35 - the node role sub-label keeps 4.4:1 in light and 4.5:1 in every dark theme
  error: 'deepseek light: rgba(20,20,20,.55) on #ffffff is 4.07:1, below 4.4:1'
# tests 45 / # pass 43 / # fail 2
```

(On the old base `4d92f4f7` the same two cases were `not ok 26`/`not ok
27` of 27 tests; only the numbering moved with main's added cases.)
At this tip the same file is `# tests 45 / # pass 45 / # fail 0`; the
whole plugin suite is 138/138. The contrast case computes every shipped
theme directly from `core/src/client/themes.js` (the same table the
themes module builds the tokens from), so it does not hardcode a second
palette.

## Knife

- put the bare `rgba(20,20,20,.55)` back → `not ok 26` (`must take its
colour from a theme token`) and `not ok 27` (`4.07:1, below 4.4:1`) → `#
pass 25 / # fail 2`; restored, `sha256` and `git diff` clean.
- `var(--bt-ink-2)` → `var(--bt-ink-3)` (the token `ThinkFlowLab#1484` had started
from) also turns `not ok 27` red at 2.2–3.1:1, which is why this does
not go back to `ink-3`.

## What I ran

At the rebased tree (`e0325ca0`, pushed as
`a9bcd021b24af91c0f477ebc4d316eca13213bb4`), base `9b866a5c`:

- `TZ=UTC ARCH_DOCS_BASE=9b866a5c npm run verify`: **exit 0** — core
**1868/1868**, social 527, tree 125, timeline 125, workflow **138/138**,
`.github/scripts` 548, interop 37, live-e2e 24; oxlint **0 errors**
(pre-existing warnings only); `arch-docs: OK (4 changed file(s))`; the
patch checks inside verify print `ok 10 patches apply cleanly in order`
and `ok core/ equals ds-bot/ + patches/ds-bot/`.
- `node .github/scripts/changelog.mjs check`: `changelog: 63 fragment(s)
in changelog.d/ are well formed`.
- Entry-level checks from the original review still hold: the `.wf-rl`
declaration is the one line changed, and `docs/subsystems/workflow.md`
gains our sentence on main's current row.

## What I did not run

- No dark-theme visual pass in a browser: the numbers above are WCAG
math over the shipped palettes, and the colour is now the theme token
the rest of the UI already uses. No `dsh web` session here.
- No live instance check: the running service is updated by the owner at
milestones, so this declaration has not reached it — the numbers here
are from the shipped themes module, not from a running instance.

## Out of scope

- Raising `--bt-ink-2`'s 0.6 alpha (a core `themes.js` token used
everywhere) to force 4.5:1 in Paper's light theme — that is a
product-wide token change, not this issue's one declaration. Recorded as
the known limit above; no separate issue is filed for it.
- The `.wf-kv` / `.wf-hist` padding and gap from `ThinkFlowLab#1484` (deliberately
untouched).

## Test placement

The two new tests sit at `@@ -1025,6 +1027,59 @@` inside
`test/client.test.js` (the `allThemes` import lands in the file header
hunk `@@ -4,6 +4,8 @@`, next to main's `rectWidth` comment) — they were
first written as an end-of-file append, where `ThinkFlowLab#1505` also appends;
moving them here leaves zero overlap, so the two pull requests merge
cleanly in either order. The move changed no product code and no
assertion.

## Checkpoint
<!-- flybot:checkpoint -->
```json
{
  "pr": 1511,
  "claim": "Dev3",
  "issue": 1495,
  "summary": "ThinkFlowLab#1484 wrote the flowchart node role sub-label's colour as a bare rgba(20,20,20,.55), so on every dark theme's card the sub-label read at 1.05-1.15:1. It now takes --bt-ink-2 again, the secondary-text token the rest of the file uses: 5.60-6.33:1 on the four dark cards and 4.42-5.11:1 in light (4.81:1 on the default light theme). Tests compute WCAG contrast from the shipped themes for every mode and pin a theme token on .wf-rl, so a hardcoded grey fails the suite. Only that declaration changes; ThinkFlowLab#1484's padding and gap are untouched.",
  "files": [
    "plugins/workflow/client.js",
    "plugins/workflow/test/client.test.js",
    "docs/subsystems/workflow.md",
    "changelog.d/1495.fixed.md"
  ],
  "checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=9b866a5c npm run verify; node --import ./test/resolve.mjs --test plugins/workflow/test/client.test.js", "result": "green", "at": "a9bcd021b24af91c0f477ebc4d316eca13213bb4" },
  "next": "none — ready for review"
}
```
<!-- /flybot:checkpoint -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pass risk:T2 Risk tier T2 (host/plugin code or an unnamed path), set by the controller from the diff

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(workflow): Settings graph Edit mode — Add link form is unstyled with blank selects; node handles are heavy black rings

1 participant