Skip to content

Collapse the borders the [c] key promised, in every port - #72

Merged
ralyodio merged 6 commits into
mainfrom
worktree-ports-collapse
Sep 8, 2026
Merged

Collapse the borders the [c] key promised, in every port#72
ralyodio merged 6 commits into
mainfrom
worktree-ports-collapse

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

c collapsed nothing outside TypeScript, and it barely collapsed there. Two
things had to be true for a seam to close, and only one of them was:

  • the library merges a seam only where two bordered siblings already touch,
    so a panel wrapped in a column was invisible to it; and
  • every panel row hard-coded a gap of 1, which collapsing leaves alone.

So the flag flipped, the app-level setting changed, and the screen looked
identical. Fixed in all six demos — TypeScript, Rust, Go, Python, C++, Zig —
plus the Ruby/PHP/Perl bindings, which replay the same frames through the C++
bridge.

The fixtures now carry both modes

demo-parity.json went from 120 cases to 240: ten screens × three themes ×
four sizes × collapsed/open. Every port replays all of them cell for cell, so
an inert c key cannot go unnoticed again. That is really the point of this
branch — the bug survived because nothing tested the other border mode.

Two bugs found on the way

A libm tie decides a pixel. At gauge sample 28/84 the angle is exactly 120°
and cos is exactly −0.5. V8 returns a hair under it (-0.50000000000000022),
Go a hair over (-0.50000000000000011); one rounds to x=16, the other to 17. A
sweep of 341 gauge sizes found 14 such divergences, all at height 6, none of
them involving collapsing at all. BrailleCanvas.pixel now snaps by ±1e-9 —
far wider than an ULP, far narrower than anything geometric — in every
language. Only demo-parity.json moved; widgets.json and screen.json
regenerate byte for byte.

A dangling capture in the C++ demo. Panel bodies are closures the UI runs at
flush(), after the screen function has returned, so [&] capturing a local
const int gap read a dead stack slot. It surfaced as a one-row layout shift in
the uncollapsed traffic screen. The gap is now read where the seam is
declared.

Bindings ABI

Additive: hqb_collapse(scene, enabled). Collapsing is a property of a scene
like its theme, not of one call, and it applies to custom trees too — so no
existing signature changed.

Verified

TypeScript 238 tests, typecheck clean
Rust full suite green
Go go test ./... green
Python unittest discover green
C++ 9/9 ctest, 240/240 parity frames
Zig 17/17 tests, 6/6 build steps
Ruby / PHP / Perl 240 exact reference frames each, both border modes
Cross-port native command + native data checks green

🤖 Generated with Claude Code

https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy

ralyodio and others added 6 commits September 8, 2026 16:34
The `c` key toggles collapse_borders in every port, and moved nothing in
any of them -- the same defect fixed for TypeScript in #57. The library
merges a seam only where two bordered siblings already touch, and every
screen laid its panels out a column apart.

No fixture noticed because all 120 demo-parity cases rendered with
collapsing off. They now render both ways, 240 cases, which is what
turns this from a thing you have to look for into a failing test.

Rust first. Every screen threads State::panel_gap(), and the wide
dashboard puts panels straight into their rows: a column is not a
bordered child, so a row of them has no seam to merge however the gap is
set. Rows that genuinely hold columns keep their gap of 1, exactly as
TypeScript does -- closing those would butt two borders together and
read as a bug rather than a collapse.

The fixture walked the port screen by screen: dashboard, then graphics,
then traffic, then components, each failing with the merged glyphs on
one side and a gap on the other until it was threaded.

Go, Python, Zig and C++ still fail these cases and are next.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy
Both ports thread panel_gap through their screens and put the wide
dashboard's panels straight into their rows, as Rust and TypeScript do.

Getting there turned up something the fixtures had never been able to
see. The Go components screen differed from TypeScript by a single
braille dot, and a sweep of the gauge across 341 sizes -- with no
collapsing involved at all -- found 14 more, every one at height 6.

The cause is not in the demo. At sample 28 of an 84-step arc the angle
is exactly 120 degrees, where cosine is exactly -0.5:

  V8    cos = -0.50000000000000022204  ->  x = 16.499999999999993  -> 16
  Go    cos = -0.50000000000000011102  ->  x = 16.5 exactly        -> 17

The two libm implementations differ by one ULP and the result lands on a
rounding tie. Neither is wrong. Rounding the raw coordinate lets that
ULP decide a pixel, so the same widget at the same size renders
differently in each language, and no amount of matching the code fixes
it.

BrailleCanvas now rounds with a tolerance far wider than an ULP and far
narrower than anything geometric, in TypeScript and in all three ports
so far. Only demo-parity.json moved: widgets.json, screen.json and the
rest regenerate byte for byte, so the blast radius is one cell in one
screen that was already inconsistent between languages.

Zig and C++ still fail the collapsed cases and are next.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy
The `c` key set a flag the layouts never read: collapsing merges a seam
only where two bordered siblings already touch, so every panel row kept
its gap of 1 and nothing moved. Thread the gap through the containers
whose children are panels, and give the reference binary a --collapsed
flag so the parity harness can cover both modes -- 240 frames now.

Read the gap where the seam is declared rather than into a local. Panel
bodies are closures the UI runs at flush, after the screen function has
returned, so a `[&]` capture of a local int dangles: the nested columns
in traffic, sessions, network, services and components read a dead stack
slot and laid out with whatever it held.

Also snap the braille pixel rounding, matching the other ports. Plot
geometry runs through cos and sin, and libm implementations differ by an
ULP at the exact angles where a coordinate lands on .5.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy
Same two blockers as the other ports. The screens wrapped each panel in a
column, so a row's children were containers rather than bordered siblings
and there was no seam to merge; and every panel row hard-coded a gap of 1,
which collapsing leaves alone. Rows now size their panels directly, through
a size carried on the screen context, and read the gap from the model.

The parity test replays the collapsed half of the fixtures too, so the
inert `c` key could not have gone unnoticed a second time.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy
The three bindings replay the same demo fixtures through the C++ bridge,
so the collapsed half of the corpus arrived with nowhere to say which
border mode it wanted. Collapsing is a property of a scene rather than of
one call, like its theme, so the ABI gains a setter -- additively, and it
applies to custom trees as well as demo frames.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy
The published catalog is generated, and the ±1e-9 snap moves one pixel in
the gauge's dial at this size -- the same divergence the ports were
disagreeing over.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy
@ralyodio
ralyodio merged commit e3a0bf9 into main Sep 8, 2026
18 checks passed
@ralyodio
ralyodio deleted the worktree-ports-collapse branch September 8, 2026 17:20
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.

1 participant