Skip to content

workflow: the drawer's Edit plane overlaps a 3+ way parallel step's cards, so a branch's out-port cannot be clicked #1677

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: bec32d5857804b1388fc031c52a21a53
Branch: dev/1-drawer-edit-overlap
Base: 1db1037
At: 2026-10-29T10:21:15Z

What happens

Flow → Edit shows the graph in the drawer's edit plane, produced by fitPlane(nodes, DRAWER_FLOW_W=364) (plugins/workflow/client.js:1034). fitPlane scales only the centre points (the #1513 rule written for the Settings canvas), so with three or more parallel branches the cards overlap. The branch out-port sits on the card's right edge (.wf-port{right:-5px}), the later card is later in the DOM with the same z-index, and the canvas has no horizontal scroll (.wf-canvas[data-fit="drawer"]{overflow:hidden}) — so clicking r1's out-port hits r2's card instead of starting a wire. The drawer cannot connect branches on a 3-way parallel graph, and the template ships one.

The read-only plane was fixed by #1663; the edit plane still fits on one line on main 1db1037a.

Reproduction (one command, six expected lines)

Root at main 1db1037a, Node ≥18. No browser, no third-party dependency, writes nothing into the repo (mkdtemp, removed at the end).

node --input-type=module -e 'import{readFileSync,mkdtempSync,rmSync}from"node:fs";import{join}from"node:path";import{tmpdir}from"node:os";const M=new Map,R={createElement:()=>null,useState:v=>[v,()=>{}],useEffect:()=>{},useRef:v=>({current:v})};globalThis.window={__ModuleLoader__:{load:({id,factory})=>M.set(id,factory(n=>n==="react"?R:{}))}};(0,eval)(readFileSync("plugins/workflow/client.js","utf8"));const C=M.get("@flybot/workflow"),{createWorkflowService}=await import("./plugins/workflow/src/service.js"),d=mkdtempSync(join(tmpdir(),"p146-")),S=createWorkflowService({dir:d}),ov=(a,b)=>Math.min(a.r,b.r)>Math.max(a.l,b.l)&&Math.min(a.b,b.b)>Math.max(a.t,b.t),bx=n=>({l:n.x-C.NODE_W/2,r:n.x+C.NODE_W/2,t:n.y,b:n.y+C.NODE_H}),D=[],V=[];for(const n of[2,3,4,6]){const ids=Array.from({length:n},(_,i)=>"r"+(i+1));S.create({name:"w"+n,nodes:[{id:"f",name:"F",prompt:"x"},...ids.map(id=>({id,name:id,prompt:"x"})),{id:"j",name:"J",prompt:"x"}],edges:[...ids.map(t=>({from:"f",to:t})),...ids.map(f=>({from:f,to:"j"}))]});const wf=S.state().workflows.find(w=>w.name==="w"+n),L=wf.layers.find(l=>l.length>1),F=new Map(C.fitPlane(wf.nodes,C.DRAWER_FLOW_W).nodes.map(x=>[x.id,x])),B=L.map(id=>bx(F.get(id))),p=[];for(let i=0;i<n;i++)for(let j=i+1;j<n;j++)if(ov(B[i],B[j]))p.push(L[i]+"/"+L[j]+"="+Math.round(Math.min(B[i].r,B[j].r)-Math.max(B[i].l,B[j].l))+"px");D.push(n+"way drawer: overlaps="+p.length+(p.length?" "+p.join(" "):""));const fl=C.flowLayout(wf,368,{}),vb=L.map(id=>{const b=fl.boxes.get(id);return{l:b.left,r:b.left+b.w,t:b.top,b:b.top+b.h}}),q=[];for(let i=0;i<n;i++)for(let j=i+1;j<n;j++)if(ov(vb[i],vb[j]))q.push(L[i]+"/"+L[j]);if(n>3)V.push(n+"way readonly368: overlaps="+q.length+(q.length?" "+q.join(" "):""))}for(const l of[...D,...V])console.log(l);rmSync(d,{recursive:true,force:true});'

Expected stdout (exactly six lines, fixed order):

2way drawer: overlaps=0
3way drawer: overlaps=2 r1/r2=82px r2/r3=82px
4way drawer: overlaps=5 r1/r2=111px r1/r3=53px r2/r3=111px r2/r4=53px r3/r4=111px
6way drawer: overlaps=14 r1/r2=134px r1/r3=99px r1/r4=65px r1/r5=30px r2/r3=134px r2/r4=99px r2/r5=65px r2/r6=30px r3/r4=134px r3/r5=99px r3/r6=65px r4/r5=134px r4/r6=99px r5/r6=134px
4way readonly368: overlaps=0
6way readonly368: overlaps=0

Notes:

  • The command builds fork → r1..rN → join through the real service, then calls the client's own fitPlane(nodes, DRAWER_FLOW_W) (the same call the drawer edit plane makes, plugins/workflow/client.js:1034) and flowLayout(wf,368) (the read-only Flow page), testing every pair of 168×72 cards for a two-dimensional intersection; Npx is the horizontal overlap width.
  • Reading: drawer 2-way 0 pairs, 3-way and up 2/5/14; at the same width the read-only page is 0 for 4-way and 6-way — so the layout function can spread the cards, this path does not.
  • 6-way is the ceiling from plugins/workflow/src/graph.js:189 ("A parallel step seats at most 6 Bots").
  • Two consecutive runs on the same box: diff empty, stderr empty, exit 0, under 1s. If numbers differ elsewhere, check the Node version (--input-type=module with top-level await).

Expected

Cards in the edit plane never overlap, and every card's out-port / mini-tool centres are on top at their own coordinates, so a wire can be started from any branch.

Scope

plugins/workflow/client.js + tests. Two options:

  • A (preferred): the editing branch reuses flowLayout's column-width / wrapping rule (same formula, wrap per layer), row heights from measured heights; the move gesture becomes a pure delta (no ÷scale).
  • B: keep fitPlane, give the edit plane horizontal scrolling with a minimum width max(364, span + 168 + 24) and overflow-x:auto, keeping the offset maths.

Either way the read-only plane and the 2-way edit plane must stay byte-identical.

Acceptance:

  1. 3/4/6-way at 364 and 368: any two cards in the edit plane have zero two-dimensional intersection, and every card's .wf-port centre and .wf-mini centre is not inside any other card's rect (DOM order decides the hit);
  2. read-only plane (368, 3/4/6-way) and the 2-way edit plane (364): serialized boxes+wires byte-identical to the base you build on;
  3. on a 3-way graph, dragging from r1's out-port to join hits r1 (the last card in DOM order at that coordinate, not r2) and adds r1→join to the edges.

Not in scope: the read-only plane (done in #1663), the sidebar row (#1488), the room/let-go items (#1664, #1666), any host-side change.

Activity

  1. HugoXK commented on Oct 29, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: bec32d5857804b1388fc031c52a21a53
    Reason: merged as 0312772 (PR #1683)
    At: 2026-10-29T13:05:15Z

  2. added 2 commits that reference this issue on Oct 29, 2026
    0312772
    7f4aa2a
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