Skip to content

Panels: draw only some border sides - #74

Merged
ralyodio merged 3 commits into
mainfrom
feat/partial-borders
Sep 8, 2026
Merged

Panels: draw only some border sides#74
ralyodio merged 3 commits into
mainfrom
feat/partial-borders

Conversation

@ralyodio

@ralyodio ralyodio commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Closes #59.

A border was all four sides or nothing. Anything else meant a divider() plus
manual padding, which gives you no corner joins and no way to say "just a top
rule" — the thing you reach for building a header strip, a sidebar rail, or a
footer that should not look boxed in.

ui.panel({ title: "Header", border: "single", sides: ["top"] }, (p) => { ... });

sides takes "all" (the default), "none", or a list. The interior follows
the sides actually drawn, so a top-only panel costs one row rather than two:

┌────────┐        ──────────        │ ab     │        ┌─────────
│ ab     │         ab               │        │        │ ab
│        │                          │        │        │
└────────┘                          │        │        │
   all              ["top"]      ["left","right"]  ["top","left"]

The collapse work already had the vocabulary

As the issue guessed, borderGlyph/borderBits speak in edge bits, which is
exactly what this needs. A corner belongs to the two sides that meet there, so
it exists only when both are drawn; where one is, the rule runs straight
through the cell the corner would have occupied. That falls out of asking for
the glyph with the bits for the sides present, with the plain rule standing in
when only one edge is set — a single edge has no glyph of its own, because that
cell is part of a run, not a corner.

Labels need the rule they sit on

A title and a subtitle live on the top rule, a footer on the bottom. None is
drawn when its rule is absent: painting a title over the first row of content is
worse than leaving it out.

Verified

All six ports, with "all" as the default so every existing frame is
identical
— which is what the suites check:

port result
TypeScript 249 tests, 11 of them new
Rust green
Go both packages
Python 36, OK
Zig 17/17, 6/6 steps
C / C++ 9/9 ctest

The new tests cover each side alone, adjacent pairs getting exactly one corner,
three sides, none, the empty list, the interior cost per rule, the title rule,
and a check that every border style renders byte-for-byte as before when sides
is "all".

🤖 Generated with Claude Code

https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy

ralyodio and others added 2 commits September 8, 2026 21:41
A border was all four sides or nothing. Anything else meant a divider plus
manual padding, which gives you no corner joins and no way to say "just a
top rule" -- the thing you reach for building a header strip, a sidebar
rail, or a footer that should not look boxed in.

  ui.panel({ title: "Header", border: "single", sides: ["top"] }, ...)

`sides` takes "all" (the default), "none", or a list. The interior follows
the sides actually drawn, so a top-only panel costs one row rather than two.

The bit vocabulary the collapse work left behind did most of it, as #59
guessed it would. A corner belongs to the two sides that meet there, so it
exists only when both are drawn; where one is, the rule runs straight
through the cell the corner would have occupied. That is borderGlyph with
the bits for the sides present, falling back to the plain rule when only one
edge is set -- a single edge has no glyph of its own because that cell is
part of a run, not a corner.

A title and a subtitle live on the top rule and a footer on the bottom, so
none of them are drawn when their rule is absent: painting a title over the
first row of content is worse than leaving it out.

All six ports. Defaulting to all four sides keeps every existing frame
identical, which the suites confirm: TypeScript 249, Rust green, Go both
packages, Python 36, Zig 17/17, C and C++ 9/9.

Closes #59

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy
Both new files imported describe/expect from "bun:test". Every other test in
the package uses node:test with assert, and for a reason: CI runs the suite
twice, once under bun and once as `node --test`, and bun:test does not exist
in the second. They passed locally and failed seven jobs.

The justify file went in with #73 before its checks came back, which is on
me; it is fixed here alongside the one it would have broken.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy
@ralyodio
ralyodio force-pushed the feat/partial-borders branch from eb742e8 to 3a33c3d Compare September 8, 2026 21:43
c_conformance segfaulted. The ctypes mirror of hq_box_options is passed by
value, so a field missing from it shifts every field after it and the C side
reads whatever is next on the stack -- for this struct, the title, subtitle
and footer pointers.

`collapse` was already missing, from before this branch. It happened not to
crash: the shifted reads landed on zeroes. Adding `sides` moved the window
far enough that they did not, which is the only reason anybody found out.

Both fields are in the mirror now, and the comment says what it has to stay
in step with. 10/10 ctest, including the conformance run that CI does and a
local build-fix directory did not have.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Df2FNu5DhinMV2soRz3cy
@ralyodio
ralyodio merged commit 101d473 into main Sep 8, 2026
18 checks passed
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.

Panels: draw only some border sides

1 participant