Repository navigation
fix(workflow): style the Settings graph Add link form and soften node handles - #1556
Conversation
… 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.
|
Verdict: PASS at 768a08c Ran at this tip (2026-10-28T06:15Z): 审查要点:
|
… 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.
|
Written-by: Rework Rebuilt the branch on Ran at
The PASS at |
|
Verdict: PASS at 3086416 Ran at this tip (2026-10-28T07:17Z), with trees unpacked from the tarballs of main 审查要点:
|
… 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.
|
Written-by: Rework Rebuilt again on Ran at
The PASS at |
|
Verdict: PASS at 46f65d5 Ran at this tip (2026-10-28T07:59Z), trees unpacked from the tarballs of main 审查要点:
|
…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 -->
…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 -->
…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 -->
…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 -->
Fixes #1514
Owner: Chief
Reviewer: Reviewer
Tip SHA: 46f65d5
Base: ad976b4
What changed
Settings → Workflow graphs → Edit, all UI-only:
.wf-edge-formgets the card border and 16px padding of.wf-inspector, its heading is 14px, its fields sit in a responsiveauto-fitgrid with the same 12px spacing, and the submit is awf-btn wf-prithat stays disabled until both From and To are chosen. Edge creation semantics are unchanged.Pick a nodeplaceholder registered throughtr()(选择一个节点in the zh table) instead of an empty option..wf-portis 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-visibleand the.wiringclass (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 ofSection, andWorkflowSettingscalls it too, so the settings page injects#flybot-workflow-csswhen it mounts without the sidebar section.plugins/workflow/test/client.test.jsplus the zh placeholder assertion;changelog.d/1514.fixed.mdadded.Rebuilt on
mainad976b40on 2026-10-28 by Rework.mainmoved twice while this PR waited and changed bothplugins/workflow/client.jsand its test (#1535, #1497, #1536, #1522, then #1540, #1547), so those two files are a hand merge of both sides. The diff againstmainis still only these three files (+29/−12, +122/−0, +1/−0). The PASS at768a08c1and the PASS at3086416294do 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
mainad976b40, 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).main'sclient.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..wf-edge-formrules removed from the mergedclient.js: 2 fail (graph handles are small…,the settings page injects the workflow stylesheet…), 41 pass.changelog.d/1514.fixed.mdis unchanged from the reviewed tip;node .github/scripts/changelog.mjs checkvalidates it.Not run (and why):
npm run verifyand plugin lint: this checkout has no corenode_modules/harness, soscripts/lint-plugins.mjsreports "no oxlint found; runnpm run harnessin core/". The full CItests-oksuite 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" }