Skip to content

fix(workflow): Settings graph view clips the second parallel card (取证) outside its frame and off the canvas - #1762

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/2-wf-graph-fit
Oct 30, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/2-wf-graph-fit

Conversation

@HugoXK

@HugoXK HugoXK commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1752

Owner: Dev2
Reviewer: Birfy
Written-by: Dev-2

What the bug was

Settings → Workflows drew the authored 960px plane at 1:1 inside a canvas that is far narrower, with the plane centred as a zero-width line, and it drew a parallel layer as one row whatever the stored y said. A workflow whose branch card was stored lower and near the right edge therefore reproduced all three reported symptoms:

  • the 取证 / 真机取证 card was cut by the canvas's right edge, its status chip reduced to Q… (view) / Que… (Edit);
  • the dashed "Parallel branch, then join" frame ran off the same edge;
  • the Claim → 取证 wire ended with its arrowhead on the clip line, and the horizontal scrollbar was the only way to see the card.

Red first

Three tests in core/test/workflows-page.test.js at the base 7f4aa2a7, run against the base sources with only the branch's test file in place (node --import ./test/harness-resolve.mjs --test --test-name-pattern="1752" test/workflows-page.test.js): 3 tests, 0 pass, 3 fail.

not ok 1 - a parallel frame encloses a member stored lower than its partner (\#1752)
  error: 'the frame 90..182 leaves forensics (159..231) outside'

not ok 2 - the Settings graph fits its plane into the canvas width (\#1752)
  error: 'the canvas wraps the plane in a fit layer'

not ok 3 - a fitted graph keeps the right-hand card and the wire into it inside the canvas (\#1752)
  error: |-
    + actual - expected

    + 'undefined'
    - 'function'

After the fix the same pattern is 3/3, and the whole file is 14/14.

Fix

  • fitScale(nodes, width) (exported pure helper): one scale() from the widest content reach (|x| + 108, so the dashed frame counts), capped at 1 and floored at 0.5 when the canvas is too narrow for half size — below the floor the canvas scrolls rather than shrinking text past readable.
  • GraphCanvas measures its canvas (getBoundingClientRect plus a ResizeObserver, the same shape the drawer's fitted plane uses) and wraps the plane in .bt-wf-fit (transform: scale(...), transform-origin: 50% 0; .bt-wf-stage takes the scaled height). Cards, dashed frames and the wire SVG all sit inside that one wrapper, so all three share the same transform.
  • The parallel frame is the union of its members' boxes: left = min(x) - 108, right = max(x) + 108, top = min(y) - 18, bottom = max(y) + 72 + 10. A member the editor stored lower is enclosed; a same-y pair gets a 100px band (was 92).
  • Dragging still commits authored plane coordinates: the pointer delta and the drop offset are divided by the current scale. The display transform never becomes the stored value. This is display-only scaling, not a room-style per-layer re-layout, so positions the user dragged stay put (the issue's Expected allows "or the graph scales to fit").

Scope and lines

Real changes, excluding the generated core/client.js, the two patches/ds-bot/files/ mirrors and patches/ds-bot/0008 (all produced by the one generate step):

file lines
core/src/client/workflows.js +47/−12
core/src/client/styles.js +3/−0
core/test/workflows-page.test.js +72/−2
docs/subsystems/core-client.md +1/−1
changelog.d/1752-settings-graph-fit.fixed.md +1/−0

139 changed lines (136 code + 3 docs/changelog), under the 150 stop-line.

Generated at this base (not counted above): core/client.js +128/−89; patches/ds-bot/0008-client-bundle.patch +1359/−1496 (whole-patch regeneration); patches/ds-bot/0003-flybot-api-client.patch +27/−24 — of which 3 lines are the real styles.js rules and 24 are hunk-header drift, kept because this patch's application result does change; patches/ds-bot/files/src/client/workflows.js +47/−12; patches/ds-bot/files/test/workflows-page.test.js +72/−2.

Drift: generate is not byte-idempotent on this box (git 2.34.1 writes hunk headers with function context). It rewrote 0001-core-fixes.patch (44/44), 0003 and 0014-flybot-branding.patch (2/2) for header reasons alone. 0001 and 0014 were restored to the base bytes; their application result was unchanged, and check stays green after the restore. 0003 keeps the generated form because it carries the 3 real CSS lines, so its remaining 24 header pairs are environment drift, not hand edits. No hunk was hand-edited, and 0008 is the whole-chain regeneration.

check, twice on this tree, prints the same three lines:

ok   ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5)
ok   10 patches apply cleanly in order
ok   core/ equals ds-bot/ + patches/ds-bot/

arch-docs runs against the rebase base 7f4aa2a70766a7292cf5474ec8a7bbc364f5d1ea; the subsystem doc line for the Settings graph is in this diff, so no Docs-not-needed: trailer is needed.

What I ran

node scripts/ds-bot-patches.mjs check            # twice, same three lines
node core/scripts/build-client.mjs --check       # silent, exit 0
TZ=UTC ARCH_DOCS_BASE=7f4aa2a7 npm run verify    # green at the tip

What I did not run

  • No browser screenshot: jsdom cannot measure text, so the chip is pinned by geometry (the chip is inside the card, and the third test keeps the card's right edge inside the scaled half-width).
  • No dev/bench.sh smoke: client-only change.
  • No #1745, #1747, #1752 sibling branches touched; this diff carries no patches/ds-bot/0002-* and no patches/ds-bot/files/src/host/* (the parallel batch's generation is untouched).

Gates at this head

Head: 575774521864af328cd68e7dbe2557f83e2cbab3 (tree 8a3c9f43862ad36b1f82b0a43f165ddb2ec26cae), base 7f4aa2a70766a7292cf5474ec8a7bbc364f5d1ea.

enqueue-guard on this head, verbatim:

enqueue #1762: no `hold` and the gate passes

The check's own decision line is the same one (enqueue #1762: no hold and the gate passes), with no PASS needed: computed tier T1, and the fork exemption for the issue reference and agent-pr — this pull request lives on a fork.

The full suite at this head (TZ=UTC ARCH_DOCS_BASE=7f4aa2a7 npm run verify, exit 0): core 2099, social 539, tree 125, timeline 129, workflow 289, .github 549, interop 37, live-e2e 24, every suite 0 fail; arch-docs: OK (10 changed file(s)); 10 patches apply cleanly in order; build-client --check silent.

Checkpoint

{"pr": 1762, "claim": "Dev2", "issue": 1752, "summary": "The Settings Workflows canvas now fits the authored graph into the canvas with one scale on a wrapper, so cards, the dashed parallel frame and the wires share the transform and nothing is clipped at the right edge; the frame is the union of its members' boxes, so a member stored lower is enclosed. Dragging divides the pointer delta by the scale, so authored coordinates are unchanged. Tests: three new cases in core/test/workflows-page.test.js (red first), 14/14 in that file; full verify green at the tip.", "files": ["core/src/client/workflows.js", "core/src/client/styles.js", "core/test/workflows-page.test.js", "docs/subsystems/core-client.md", "changelog.d/1752-settings-graph-fit.fixed.md"], "checks": {"ran": "TZ=UTC ARCH_DOCS_BASE=7f4aa2a7 npm run verify", "result": "green", "at": "575774521864af328cd68e7dbe2557f83e2cbab3"}, "next": "none — ready for review"}

)

The Settings canvas drew the authored 960px plane at 1:1, so a branch card stored near the right edge, its dashed frame and the wire into it were clipped; and the frame took its top from the topmost member with a fixed 92px height, so a member stored lower sat outside it. The canvas now measures itself, scales the whole plane (cards, frames and wires share the transform, never below half size), and the frame is the union of its members' boxes. Drags still commit authored coordinates by dividing the pointer delta by the scale.
@Birfy
Birfy added this pull request to the merge queue Oct 30, 2026
Merged via the queue into ThinkFlowLab:main with commit a6ba328 Oct 30, 2026
14 checks passed
@Birfy
Birfy deleted the dev/2-wf-graph-fit branch October 30, 2026 03:18
Birfy pushed a commit to HugoXK/FlyBot that referenced this pull request Oct 30, 2026
… so the queue stalls after every delivery (ThinkFlowLab#1745)

Fixes ThinkFlowLab#1744

Owner: Dev2
Reviewer: Birfy
Written-by: Dev-2

Head: `572606461b6fb87cab62873e2215a880a18f0b3d` (tree
`f69ff5a600f7ca665fb7006986a76146c562f72b`), base
`8252bce955f847b1ddeb49566635c3f8cd961bbc`.

## What changed

A delivered card (`result-ready`) still counted as work in progress, so
it filled the member's one WIP slot: after every delivery the member
could not claim the next queued item, and `task-drive` offered that item
to another member — or to nobody in a one-member role.

One predicate answers both call sites. `occupiesSlot(task)` sits beside
`columnOf` in `core/src/shared/task-board.js` and is true only for
`claimed` and `working`. `claimQueued` (`core/src/host/tasks.js`) and
`holdsCard` (`core/src/host/task-drive.js`) call it; the expression now
exists nowhere else. The `task-drive` comment says a delivered card
waits on its requester, and the old comment that named only `blocked` is
gone — that comment is where this bug was born.

The board reading is unchanged: `columnOf` still maps `result-ready` to
`in-progress`, so the card stays **In progress** until it is accepted.
`client/kanban.js` (Complete button) and `host/project-assistant.js`
(digest) are untouched, as are the send-back rules.

## Rebase onto `8252bce9`

The old tip went `dirty` when `ThinkFlowLab#1762` (Settings graph fit) merged: the
two pull requests shared `core/client.js`,
`patches/ds-bot/0008-client-bundle.patch` and
`docs/subsystems/core-client.md`. `ThinkFlowLab#1761` then landed
(`core/src/host/project-assistant.js`, `docs/subsystems/core-host.md`),
which is also why this branch was rebased onto the live `main` at push
time.

- The only conflict was `0008`. Nothing in `patches/ds-bot/` was
hand-edited: the base copy was taken, then the whole chain was
regenerated from the merged sources (`cd core && npm run build`, then
`node scripts/ds-bot-patches.mjs generate`).
- Both `docs/` files merged as text, keeping `main`'s sentence and this
branch's sentence in each.
- The source delta is unchanged by the resolution: **112** real lines
(98 added / 14 removed), the same as before the rebase and far under
150.

## Red first (on `8252bce9`, the rebased base, sources untouched)

Three `not ok` from `core/test/task-queue.test.js` plus the
shared-module unit test, which cannot even import the export:

```
not ok 1 - test/bot-tasks.test.js
  SyntaxError: The requested module '../src/shared/task-board.js' does not provide an export named 'occupiesSlot'
```

```
not ok 5 - a delivered card does not hold the member's WIP slot (ThinkFlowLab#1744)
  error: |-
    The input did not match the regular expression /^Claimed task-4192c7315ecaf26e /. Input:

    'No queued item for Dev right now. You already hold an in-progress card (task-03819cdfab971c48).'
```

```
not ok 6 - the wake pass offers the queue to a member whose card was delivered (ThinkFlowLab#1744)
  error: |-
    the member whose card was delivered is offered the queue item

    1 !== 2
```

```
not ok 7 - a send-back returns the card as work and the next claim is capped (ThinkFlowLab#1744)
  error: |-
    The input did not match the regular expression /^Claimed task-235ac0df90fd8adf /. Input:

    'No queued item for Dev right now. You already hold an in-progress card (task-e8fc9c4a968ea161).'
```

The existing WIP test ("a member that already holds an in-progress card
claims nothing") is unchanged and stays green: WIP is still enforced at
the claim, only `result-ready` and `blocked` no longer count.

## Lines

Real changes, excluding the generated `core/client.js`, the five
`patches/ds-bot/files/` mirrors and `0008`:

| file | lines |
| --- | --- |
| `core/src/host/task-drive.js` | +6/−6 |
| `core/src/host/tasks.js` | +4/−4 |
| `core/src/shared/task-board.js` | +8/−0 |
| `core/test/bot-tasks.test.js` | +15/−1 |
| `core/test/task-queue.test.js` | +61/−0 |
| `docs/subsystems/core-client.md` | +1/−1 |
| `docs/subsystems/core-host.md` | +2/−2 |
| `changelog.d/1744-result-ready-slot.fixed.md` | +1/−0 |

**112** changed lines (105 code + 7 docs/changelog).

Generated at this base, listed separately: `core/client.js` **+3/−0**;
`patches/ds-bot/0008-client-bundle.patch` **+4/−1**; mirrors
`patches/ds-bot/files/src/host/task-drive.js` +6/−6,
`files/src/host/tasks.js` +4/−4, `files/src/shared/task-board.js` +8/−0,
`files/test/bot-tasks.test.js` +15/−1, `files/test/task-queue.test.js`
+61/−0.

`0008` and `client.js` are a **real** bundle change, not drift: the new
`occupiesSlot` export is in the module the client bundle carries, so
`client.js` gains those three lines and the patch's hunk count moves by
one (`+2805,4192` → `+2805,4195`). A no-change `generate` afterwards
left `0008`, `0003`, every mirror and the source tree byte-identical.

`generate` is still not byte-idempotent on this box (git 2.34.1 writes
hunk headers with function context): after the rebuild it rewrote
`0001-core-fixes.patch` (44/44) and `0014-flybot-branding.patch` (2/2)
for header reasons alone. Both were restored to the base bytes — the
application result was unchanged — and a second, no-change `generate`
then rewrote **only** those two headers, with `0008` and every other
patch unchanged. No hunk was hand-edited.

## Check

`check`, twice on this tree, prints the same three lines:

```
ok   ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5)
ok   10 patches apply cleanly in order
ok   core/ equals ds-bot/ + patches/ds-bot/
```

`arch-docs` ran against the rebase base
`8252bce955f847b1ddeb49566635c3f8cd961bbc`: **OK (15 changed file(s))**.

## What I ran and what I did not run

Ran at the tip: `node scripts/ds-bot-patches.mjs check` (twice), `TZ=UTC
ARCH_DOCS_BASE=8252bce9 npm run verify` (exit 0), and the red-first run
at the base with only the two test files in place.

The full verify at this head: core **2108**, social **539**, tree
**125**, timeline **129**, workflow **289**, `.github` **549**, interop
**37**, live-e2e **24**, every suite 0 fail; `arch-docs: OK (15 changed
file(s))`; `10 patches apply cleanly in order`; `build-client --check`
silent.

Not run: no browser screenshot (host-side queue rule, no visual
surface); no `dev/bench.sh smoke` (no `index.js` / `src/host/` wiring
change beyond the predicates, and it would need a bench). No other pull
request or branch was touched.

## Gates at this head

`enqueue-guard` at this head is red, verbatim:

```
do not enqueue ThinkFlowLab#1745: comment by Birfy: names 89bfbf9, not the tip 5726064 — the change set differs at core/client.js, so that conclusion does not describe this head; comment by Birfy: names 3b0ff87, not the tip 5726064 — the change set differs at core/client.js, so that conclusion does not describe this head
```

That is the guard rejecting both standing conclusions as describing
other heads — the two shas inside that sentence are the stale verdicts,
not evidence for this tip. Labels it read at this head: `risk:T3`,
`pass`; the fork exemption covers the issue reference and `agent-pr`. A
PASS naming `572606461b6fb87cab62873e2215a880a18f0b3d` is what clears
it.

## Checkpoint

<!-- flybot:checkpoint -->
{"pr": 1745, "claim": "Dev2", "issue": 1744, "summary": "A delivered
card (result-ready) no longer fills the member's WIP slot. One
predicate, occupiesSlot in core/src/shared/task-board.js, is true only
for claimed and working, and both call sites (claimQueued, holdsCard)
use it; the column reading still shows result-ready as In progress. The
branch was rebased onto live main after ThinkFlowLab#1762 and ThinkFlowLab#1761 merged, with the
generated files regenerated from zero and no hand-edited patch hunk.
Tests: three new task-queue cases plus a shared-module test, red first;
full verify green at the tip.", "files":
["core/src/shared/task-board.js", "core/src/host/tasks.js",
"core/src/host/task-drive.js", "core/test/task-queue.test.js",
"core/test/bot-tasks.test.js", "docs/subsystems/core-host.md",
"docs/subsystems/core-client.md",
"changelog.d/1744-result-ready-slot.fixed.md"], "checks": {"ran":
"TZ=UTC ARCH_DOCS_BASE=8252bce9 npm run verify", "result": "green",
"at": "572606461b6fb87cab62873e2215a880a18f0b3d"}, "next": "none — ready
for a PASS at this tip"}
<!-- /flybot:checkpoint -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(workflow): Settings graph view clips the second parallel card (取证) outside its frame and off the canvas

2 participants