Repository navigation
Commit 6fba9e9
committed
fix(er): give each relationship end on a crowded row its own port (#540)
## What changes
Round 24's only visual major for Sonnet was saas-workspace-er s5: five relationships end on `organizations.id`. A table row holds two ends on a side cleanly, 22 apart. A third was squeezed in between them, 11 apart. Excalidraw draws each bar or crow's foot 17 across, so the three bars ran into one vertical stroke, and the reader could not tell which end belonged to which line.
**A crowded row grows so each end has its own port.**
- Once the placement is chosen, a row holding more than two ends on a side grows by two lines (56) for each further end. Its ends then sit a port step (22) apart around the row's middle.
- Two lines keeps the outermost port as close to its own field, relative to the next row's field, as either of two ends on a plain row (about 2:3). That way the line still reads as attached to the right column. At one step (50) instead, the outer port of s5's row sat 17 from `name` and 44 from `id`.
- A last row holding two ends on a side makes room below them (8), clear of the corner. Before, a one-row table's two ends were clamped 15 apart.
- The tables are placed again in the same arrangement: the same columns and the same order within them.
- Rows align on the ports the lines will take, so a line between level rows still runs straight (s5's `labels` line).
- The layout takes optional `parentPort`/`childPort` and an `arrangement` to keep.
- A grown row draws one text per cell (`text:<table>:<column>:<row>`). Empty cells are left out, since the scene schema rejects empty text. Tables without a grown row keep one text per column, and `erDrawnCheck` reads both forms.
- **Guard (as #447/#524):** the grown drawing is kept only if it draws no more:
- crossings, reader findings or detours (longer than twice the direct way plus 120);
- ends wrapping their own table, or tables a line runs past;
- marks on labels;
- and no longer a long side.
Otherwise main's drawing stands.
**Loops nest inside lines to other columns.** Among the ends climbing (or dropping) from one row side, a loop to a table in the same column now takes the port nearest its turn. Before, rise alone decided. In s5, the `api_keys` loop took the lower port and `projects`' line, which turns far out by `projects`, crossed it. That was s5's crossing, and it is gone.
Candidate selection is unchanged. Growth repairs the chosen placement and never re-ranks placements. Letting growth into selection let the first layered candidate win on its margin with long parallel runs: 20 fuzz schemas worse.
## Measured
ER corpus: all 46 distinct ER specs from Oct 17–23 evals, including r24. Branch against main 42b2d80.
| | main | branch |
|---|---|---|
| byte-identical | | 39 / 46 (the 7 saas-workspace-er specs change) |
| renders worse on crossings, findings, detours, lines through a table or head, marks on labels | | 0 |
| ends sharing an anchor (two ends on one side under 20 apart) | 18 | 0 |
| marker overlaps (bars, feet or rings touching) | 18 | 0 |
| crossings | 9 | 2 |
| reader findings | 12 | 2 |
| detours | 0 | 0 |
| line length / drawing area | | +0.6% / +2.9% |
| render time, all 46 (interleaved, min of 3×3) | 885 ms | 899 ms (+1.5%) |
- **s5 spec** (also s2–s4 and 80 other corpus runs):
- crossings 1 → 0, reader findings 2 → 0, shared anchors 2 → 0;
- `organizations.id` grows once (3 left, 2 right); 1532×988 → 1532×1044;
- 116.7 → 115.3 ms.
- **The other saas variants:**
- Two place `users.id` and `organizations.id` three to a side. Both rows grow once; crossings 2 → 1, shared anchors 4 → 0.
- The remaining saas variants share s5's layout and change the same way.
ER fuzz at full seeds (240 schemas), against main:
- 0 schemas worse on crossings, findings, detours, lines through a head, marks crossed by another line, or marks on labels. 191 / 240 byte-identical.
- Shared anchors 122 → 24, marks another line runs through 58 → 20.
- Crossings 69 → 61, findings 171 → 150, detours 25 → 23. Line length −0.3%, area ±0.
- Render time 6.68 → 6.91 s (+3.5%); +8% on the 49 changed schemas.
- Class and org-chart rendering are untouched (they do not import `er.ts` or `er-layout.ts`).
Stress fixtures: marketplace crossings 13 → 9 and bunched ends 6 → 0. saasWorkspace crossings 1 → 0, findings 2 → 0, bunched 2 → 0.
## Proof
- Failing-first tests:
- From the real s5 spec (`er.test.ts`): no bunched ends; each of the five ends inside the id row's band and nearer `id` than `name` by the 2:3 rule; `labels` straight; 0 crossings. It failed on main with 2 bunched pairs.
- A one-row table's two ends a port step apart, clear of the corner (failed on main: 15 apart).
- `saasWorkspace` fixture pin tightened: crossings ≤ 1 → 0, bunched ends ≤ 2 → 0.
- Code Mode (`agent/er.test.ts`): a five-way key row builds artifacts that decode as the CLI and MCP read them. The per-cell texts are present, with no empty cells. A local CLI render caught this: the first cut emitted empty key cells.
- `erDrawnCheck` reads per-cell tables, and still fails a renamed cell.
- ER fuzz pins bunched neighbor ends: 0 in CI's 16 schemas (main 3), at most 18 over the full 240 (main 99).
- Local runs, all passing:
- `pnpm nx affected -t typecheck,test,build --base=origin/main` (10 projects);
- `pnpm run check`;
- `pnpm nx build-storybook diagram-ui`;
- full `er-fuzz`.
- PNGs inspected locally for s5 and f8f5 (both saas layouts).
## Open
- 18 bunched neighbor pairs in full fuzz (9 schemas, 5 distinct) keep main's drawing, because growth there would add a reader finding: mostly a step that becomes a port jog, or a corridor. Schema 136 would trade two crossings for one jog.
- A name band (entity-level ends) holding three ends on a side still bunches. The band does not grow.1 parent 42b2d80 commit 6fba9e9
8 files changed
Lines changed: 676 additions & 90 deletions
File tree
- .changeset
- packages/diagram
- agent/src/lib/code-mode
- excalidraw/src/lib
- renderer/src
- diagram-types
- scenarios/src/lib
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
| 8 | + | |
9 | 9 | | |
10 | 10 | | |
11 | 11 | | |
| |||
93 | 93 | | |
94 | 94 | | |
95 | 95 | | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
96 | 129 | | |
97 | 130 | | |
98 | 131 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
185 | 185 | | |
186 | 186 | | |
187 | 187 | | |
188 | | - | |
189 | | - | |
190 | | - | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
191 | 191 | | |
192 | 192 | | |
193 | 193 | | |
| |||
251 | 251 | | |
252 | 252 | | |
253 | 253 | | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
| 265 | + | |
| 266 | + | |
| 267 | + | |
| 268 | + | |
| 269 | + | |
| 270 | + | |
| 271 | + | |
| 272 | + | |
| 273 | + | |
| 274 | + | |
| 275 | + | |
| 276 | + | |
254 | 277 | | |
255 | 278 | | |
256 | 279 | | |
| |||
310 | 333 | | |
311 | 334 | | |
312 | 335 | | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
| 340 | + | |
| 341 | + | |
313 | 342 | | |
314 | 343 | | |
315 | 344 | | |
| |||
348 | 377 | | |
349 | 378 | | |
350 | 379 | | |
| 380 | + | |
| 381 | + | |
| 382 | + | |
| 383 | + | |
| 384 | + | |
| 385 | + | |
351 | 386 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
| |||
171 | 172 | | |
172 | 173 | | |
173 | 174 | | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
| 192 | + | |
| 193 | + | |
| 194 | + | |
| 195 | + | |
| 196 | + | |
| 197 | + | |
| 198 | + | |
| 199 | + | |
| 200 | + | |
| 201 | + | |
| 202 | + | |
| 203 | + | |
| 204 | + | |
| 205 | + | |
| 206 | + | |
| 207 | + | |
| 208 | + | |
| 209 | + | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
| 260 | + | |
| 261 | + | |
| 262 | + | |
| 263 | + | |
| 264 | + | |
174 | 265 | | |
175 | 266 | | |
176 | 267 | | |
| |||
454 | 545 | | |
455 | 546 | | |
456 | 547 | | |
457 | | - | |
| 548 | + | |
458 | 549 | | |
459 | 550 | | |
460 | | - | |
461 | | - | |
462 | | - | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
463 | 587 | | |
464 | 588 | | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
465 | 624 | | |
466 | 625 | | |
467 | 626 | | |
| |||
0 commit comments