Skip to content

fix(canvas): Pool, Register and Parameter value rows sit too close to node silhouettes #332

Description

@MerciHanrim

Summary

In a Pool, a Register and a Parameter, the title starts after its kind chip, but the rows under it start at the content edge. A Pool's value sits 14 px before its title text and, on the Pool's slanted side, on or across the outline; a Register's value sits 13 to 28 px before its title text and inside the keyboard-focus ring. This has been so since the first implementation; it is not a regression of #325 or v0.21.0. Planned as v0.21.2: Option C plus minimal widening, with no residual overlap, for these three kinds.

Measured

Read-only, on main 7e66da2 (dev server) and production v0.21.1 · build 7e66da2 (real UI; the two agree within 0.6 px on every node), in Chrome, desktop and phone, light, dark and forced colours. Per row: the glyphs' real extent (a DOM Range), the silhouette's fill edge at that row's height (isPointInFill), the clearance between them, and the start relative to the title text. A synthetic graph with every kind and the values 0, 90, 189, -12.5, 3.14, 1234567.89, a long unit and an invalid Register, plus every node of the Coffee, gacha and early MMO templates (183 nodes).

Kind, row Clearance to the silhouette now (min / median / max, px) Start vs the title text Note
Pool, value -9.7 / -1.0 / -0.5 -14 digits on or across the slanted outline; inside the focus ring by 6.5 to 15.7
Pool, capacity (≤ N) -1.8 / 1.0 / 2.3 -14
Register, value 3.0 / 3.5 / 10.0 -13 to -28 inside the focus ring by up to 3 in the templates
Register, = expr 8.0 / 16.3 / 16.3 -14 ends in an ellipsis by design
Parameter, value 7.0 / 7.0 / 18.0 -10 to -16
Parameter, unit 7.0 -14
  • The same in every state: selection, keyboard focus, invalid, Focus mode and the activity overlay move no row.
  • The canvas is physically left-to-right in every language (docs/localization.md §L9.2), so ar measures the same; a row can still resolve its own text direction to RTL (an Arabic unit with dir="auto").
  • L1 hides the detail rows and L0 the whole body.

Measured, deferred

Kind, row Clearance to the silhouette now (min / median / max, px) Start vs the title text Note
Source / Drain, mode line -12.5 / 1.3 / 3.2 -14 sets the node's width in 7 of the template nodes
Converter, mode line -14.0 / -8.5 / -7.5 -14 outside the outline

The silhouette is drawn with preserveAspectRatio="none", so its arrow point, notches and slant grow in proportion to the node's width. Applying the contract below to these rows widened 37 template nodes by up to 49 px and made the title clearance worse on 33 of them. They need a silhouette whose point, notch and slant depths stay a fixed number of px, which is separate structural work.

Contract: Option C plus minimal widening

It applies only to these rows: a Pool's current value and capacity, a Register's result and its = expr preview, a Parameter's value and unit.

  1. Each row starts where the title text starts: the chip plus its gap, 14 px from the content edge.
  2. Each row's glyphs are at least 8 px inside the silhouette at the row's own height (the 6 px focus ring plus 2), which also clears the slanted Pool side.
  3. Both are measured from the physical start of the node's stack, not from the row element's own dir="auto" direction.
  4. When the node's current width already satisfies 1 and 2, its size does not change.
  5. When it does not, only that node widens, by the minimum that satisfies 1 and 2.
  6. A very long user string, a long = expr included, still follows the existing 260 px maximum width and ellipsis rules; nothing widens without bound. At the maximum width, no glyph may cross the silhouette. A row that would not fit whole even at 260 px does not widen its node; it ends in its ellipsis at the current width.
  7. Title clearance: a title that had 8 px or more keeps at least max(8 px, its clearance − 0.5 px); a title that had less than 8 px loses none of it.
  8. Height comes first: a widening that would unwrap a title (and so change the node's height) does not happen; the row ends in its ellipsis instead. Where the title rule or the height rule stops a widening, the row is cut the same way, and the cut is visible (1234567.89 shows as 12345…). Only the display is cut; the stored value, the file and every calculation are unchanged.
  9. Unchanged: node positions, heights, saved coordinates, the engine, files and digests. On a node whose width actually changes, its ports and the drawn routing move with the new outline; nothing else moves.
  10. Residual overlap: 0. No row of these three kinds crosses its silhouette.
  11. Source, Drain, Converter, Gate and End rows are unchanged.

Implementation constraint

  • No per-frame measurement. The offsets and any widening are computed only when a node's content, the language or the node's size changes, inside the existing NodeFrame measurement, and cached there. Any change of a row's actual text (digits, unit, sign, decimal point, expression) is read again at the next settle; a playback animation frame never is.

Expected impact

  • Measured after implementation: in the three templates, exactly one node widens, the early MMO Register r_income, by 5.66 px (126.84 to 132.5 px). The estimate before implementation was 4 Registers by up to about 14 px; rule 6 keeps the rest at their width. Node heights, positions and every drawn connection in the templates are unchanged.
  • Visual baselines that show these node bodies at L2 or L1 change; L0 ones do not. The baselines are updated only after the actual failing list has been reviewed against its before images and approved.

Tests

  • A new end-to-end spec that reads the drawn geometry of every Pool, Register and Parameter row: its start is at the title text's start or later; its clearance at its own height is at least 8 px at both ends; zero rows cross a silhouette in the synthetic graph and every template; node heights and positions equal main's, and widths equal main's except the pinned list; the same selected, focused, invalid and in Focus mode; an Arabic case; a measurement counter that does not move per animation frame.
  • A unit test for the fill the silhouette leaves at a given height, and for the fit itself.
  • The existing value-driven Register test (a Register never clips its own number) and the long-label tests keep passing.

Out of scope

Activity

  1. changed the title [-]fix(canvas): value and detail rows can cross node silhouettes[/-] [+]fix(canvas): Pool, Register and Parameter value rows sit too close to node silhouettes[/+] on Oct 7, 2026
  2. MerciHanrim commented on Oct 7, 2026

    @MerciHanrim
    OwnerAuthor

    Scope narrowed during implementation, from measurement. Applying the contract to every value and detail row widened 37 template nodes by up to 49 px and made the title clearance worse on 33 of them, almost all from the Source, Drain and Converter mode lines: their pointed and notched silhouettes grow in proportion to the node's width, so 8 px of clearance comes back only slowly.

    This issue now covers the Pool, Register and Parameter rows only. The mode lines are kept in the body as measured and deferred; they need a silhouette with fixed-px point and notch depths, which is separate structural work. The v0.21.2 plan is unchanged.

  3. MerciHanrim commented on Oct 7, 2026

    @MerciHanrim
    OwnerAuthor

    Shipped in v0.21.2 by #333, squash-merged as c5e7d8f on 2026-10-07 at 23:53 Seoul time (14:53 UTC). Its tree is identical to the approved pull request head 8e05244.

    • Scope delivered: a Pool's value and capacity, a Parameter's value and unit, and a Register's result and = expr preview start where the title text starts and stay at least 8 px inside the drawn outline at both ends. A node widens only by the minimum a whole row needs, up to 260 px; otherwise the row ends in a visible "…". Node heights, positions, files, digests and simulation results are unchanged.
    • Pull request CI: the third run at 8e05244 passed every job without a retry; the five shards ran exactly the 2,013 listed tests, each once (2,006 passed, 7 skipped by design, 0 failed, 0 retried); the production bundle 16 of 16, the PWA 19 of 19. The two earlier failed runs are recorded in fix(canvas): Pool, Register and Parameter value rows sit inside the node (v0.21.2) #333.
    • Main CI (run 37640510667 at c5e7d8f): every job that ran (checks, the five shards, the production bundle and the PWA) and every test passed: the 2,013 listed tests each ran once, 0 failed, 0 retried; the production bundle 16 of 16, the PWA 19 of 19. GitHub did not create the final aggregate e2e job, so the workflow alone ended as failure. One approved re-run of the failed jobs was refused by GitHub with an HTTP 500 (likely because no job had failed), and no further re-run was made. This is recorded as a GitHub aggregation exception; if it repeats on the next main run, it gets its own CI issue.
    • Production: https://cozy-loop-studio.pages.dev serves v0.21.2 · build c5e7d8f (the desktop toolbar label and the phone's ⋯ menu). An automated check through the UI only, in fresh Chrome contexts, passed 25 of 25: no dev bridge, no page error, and every Pool, Parameter and Register row in view (a synthetic graph and the three templates; light, dark, forced colours and a phone; 120 rows) at least 8 px inside the outline and starting at the title, the smallest clearance 8.06 px.

    Deferred, as decided in this issue: the Source, Drain and Converter mode lines, the Gate's rows and two-line Pool titles, which need fixed-depth silhouettes and are separate work.

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

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions