Skip to content

client: at the factory row pitch a forward wire is drawn through the cards of the next row (#2161 regression) #2164

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: a6dd7dd7e74b4328a9bec47d8a8ce344
Branch: dev/2-forward-wire-factory-pitch
Base: 96e24ef
At: 2026-11-02T12:55:38Z

What goes wrong

#2161 (merged as 96e24ef307e034f3be2706ac60a6631cc8db0e24, 12:54:38Z) made a row-crossing wire measure its corridor from the row's lowest measured bottom. In the layout the app actually produces, a row's cards can be taller than the row pitch, so that bottom is already below the next row's top: the corridor gets clamped to zero and lands inside the cards of the row below.

Concretely, with the factory pitch (plugins/workflow/src/graph.js:10 GAP_Y = 108, layoutNodes places a second row at y = 108):

card position height
a (0, 0) 48
b (188, 0) 120
c (0, 108) 72
d (188, 108) 72

Edge a → c:

  • at 0bfaf525 (before #2161): M 0 48 L 0 108 — straight down, clean;
  • at 96e24ef3 (now): M 0 48 L 0 120 L -96 120 L -96 108 L 0 108 — the run at y = 120 sits inside c (its body is 108–180), and the final approach rides c's top border.

Same layout, edge a → d: one card crossed before (b, which is what #2161 set out to fix), three now (b, c, d). Heights of 140 and 160 behave the same; at 108 the corridor collapses onto the target's top border.

Cause: rowBottom = max(bottom of boxes whose top is within 1.5px of the source's top) is 120 here, while to.top is 108, so gap = min(WIRE_GAP, max(0, (to.top − rowBottom) / 2)) is 0 and yA = rowBottom. #2161's two tests place the second row at y = 120 / y = 400 (a 280 pitch), so they never exercise a row that starts above the previous row's tallest bottom.

Reproduction

Build the graph above with normalizeGraph / the page's own layout, then read the wire's d for a → c: it contains a horizontal run at y = 120, inside c. Same with h = 140 / 160; h = 108 puts the run on c's top border.

Expected

  • A forward wire never enters or rides the border of a non-endpoint card in either row, including when the next row begins above the source row's tallest bottom.
  • The #2161 case still works: with a 96–120px card beside a 48px source and a row far enough below (the 280 pitch used by its tests), the wire leaves above the neighbourhood and does not cross the neighbour.
  • No layout that was clean before #2161 becomes dirty: for every graph, the number of cards a wire crosses must not increase relative to 0bfaf525.
  • The same-row (loop/skip) and back-edge geometries keep their current d strings verbatim (#2151 10 cases, #2138 6 cases).

Suggested fix shape

When the row's lowest bottom leaves no room for a corridor — to.top − rowBottom < 2 × WIRE_GAP — fall back to the pre-#2161 corridor (source-only, clamped to stay above to.top), so factory-pitch graphs render exactly as they did before; keep the row-measured corridor only when there is room. The guard must also make yA > yB unrepresentable.

Scope

core/src/client/workflows.js, core/test/workflows-page.test.js (one test on the factory 188/108 pitch with a 120px card, not a hand-picked 280 pitch), core/client.js + patches/ds-bot/0008-client-bundle.patch + the two patches/ds-bot/files/ mirrors via build + generate, docs/subsystems/core-client.md sentence, changelog fragment.

Duplicate check

row pitch wire (0), GAP_Y (2: this regression's own PR #2161, closed, and an unrelated panel issue #1513), "next row" wire in titles (0). No other issue covers the factory-pitch collapse; this is a follow-up to #2160, not a duplicate of it.

Activity

  1. HugoXK commented on Nov 2, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: a6dd7dd7e74b4328a9bec47d8a8ce344
    Reason: merged as 7d06d01 (#2172)
    At: 2026-11-02T14:16:57Z

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions