fix(breaking): bordered elements should reserve layout space for border sides - #116
Conversation
Clay doesn't account for border widths in layout — borders were drawn as visual overlays, so any element with border width > padding had its content collapsed behind the border glyphs (or, for fit-height boxes, collapsed to a single row with top and bottom glyphs overlapping and children invisible). Fix: at pack time, compute effective padding per side as max(userPadding, borderWidth). Border glyphs draw in the same cells as before; only the Clay layout values change so the engine reserves those cells. Semantics of the max rule: - No explicit padding: border width becomes the effective padding; content lands inside the border, not behind it. - padding == borderWidth (prior workaround): max evaluates to the same value, no double-reservation; these elements render identically. - padding > borderWidth: extra padding provides breathing room inside the border, measured from the border edge inward. Callers who set padding == borderWidth as a workaround are unaffected. Downstream compensators (e.g. lgtm.shop Panel) will render identically until they drop the manual compensation on their next pin bump. Resolves Open Decision #4 in specs/renderer-spec.md.
commit: |
|
Size Increased — +0.1 KB 118.9 KB unpacked |
Border presence implies padding on that side equal to the border width. Callers who compensated by setting `padding == borderWidth` now receive double-reservation and must remove the workaround padding. `padding: 1` with `border: 1` → effective 2; `border: 1` alone → effective 1.
cowboyd
left a comment
There was a problem hiding this comment.
@natemoo-re It'll be sooo nice to bury this footgun in the ground once and for all.
I do have one thought: what would it look like to put this in C? It isn't a hard requirement, but I'm thinking towards the day when we get uplift from a TUI framework in a totally different language using TTY (similar to the way I'm hoping we get uplift by being a shared tool amongst JS web framework TUIS).
If it isn't feasible, no worries, just wanted to put that there as something to think about.
The additive effective-padding rule (userPadding + borderWidth per side) was applied in pack() on the TypeScript side, so the packed layout word carried a pre-computed value. It now happens in clayterm.c when the PROP_BORDER block decodes border widths: decl is zero-initialized and PROP_LAYOUT decodes first, so border-without-layout and layout-without-border both fall out naturally. The wire format's padding field now carries raw user padding; the renderer owns the reservation. Behavior is unchanged — all existing border tests pass as-is.
|
@natemoo-re a rare case where it seems to actually add clarity to have moved something into C! Its seems to me that we said the next thing was to dream up a bunch of use-cases to expose lurking dragons? |
|
@cowboyd yes, moving to C was a great call! yep, I think dogfooding will expose plenty more rough edges but this feels like one big footgun down! |
previously, bordered elements collapsed when no padding was set. Clay treats borders as visual overlays and does not account for them in layout, so text in the terminal was rendered behind the border glyph
now, tty intercepts borders at
pack()time and passes an effective padding to Clay that equalsuserPadding + borderWidthper sidebreaking change: border presence now implies a minimum padding equal to the border width. callers who compensated by setting
padding == borderWidthnow get double-reservation and should remove the workaround padding!