Skip to content

css+layout: implement a scoped subset of multi-column layout (columns) - #244

Merged
tannevaled merged 1 commit into
mainfrom
multicol-layout
Sep 28, 2026
Merged

tannevaled merged 1 commit into
mainfrom
multicol-layout

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Summary

  • pkg.go.dev's own "Source Files" list (.UnitFiles-fileList{columns:12.5rem 5;...}) rendered as one long single column instead of up to 5 narrow ones. Round 143 found this real but deferred it as too large; this round re-read the actual CSS Multi-column Layout spec mechanics before trusting that estimate and found the one confirmed trigger narrower than it looked: a <ul> of short, uniform-height <li>s, not arbitrary flowing prose needing mid-paragraph fragmentation.
  • Scoped to exactly that shape: each in-flow element child is laid out once at the real column width (fully atomic, never split across columns — the same convention this engine already applies to a replaced element or a table row), then a single greedy cumulative-height pass buckets children into N columns and repositions each already-correct box with the existing translateBox helper. No true iterative balancing, no column-span, no column-rule painting, no fragmenting a child's own internal content across columns.
  • Reuses the existing column-gap field shared with flex/grid rather than adding a new one (real CSS defines it once, in the Box Alignment module) — its shared "unset" default moved from the Go zero value to Length{Auto:true} so multicol can resolve it differently (1em, not flex/grid's 0) without touching their own behaviour, confirmed via their own gapLen/gapPxCSS treating Auto and explicit-0 identically.
  • Caught along the way: two hand-built fallback css.Style{} literals (place()'s and table.go's cellStyle()) never set ColumnWidth, so its Go zero value read as "column-width:0" to any code checking !Auto — breaking five pre-existing Shadow DOM slot-distribution tests. Fixed both to set it explicitly, matching every other Auto-defaulted field in those same literals.

Test plan

  • go build ./..., go vet ./..., go test ./... all green
  • Coverage floors held (layout back to its 100.0% ratchet floor)
  • 20 new tests: css/parse_test.go (column-count/column-width/columns — every value shape, the || order-independence, invalid shapes, unset, explicit inherit, inheritFrom) and the new layout/multicol_test.go (resolveColumns tested directly for every resolution case, plus full-pipeline tests for balanced columns, column-width alone, the real trigger's own narrower-than-requested shape, display:none/out-of-flow/bare-text children correctly excluded, an overlong child staying whole, and column-count:1 falling back to ordinary block stacking)
  • Git-stash-confirmed: reverting the production files alone (keeping the new tests) fails to even compile in both css and layout
  • Verified directly against the real live page: re-rendering pkg.go.dev/net/http now lays its Source Files list out in 4 columns (fewer than the CSS's literal 5, because the container is narrower than 5×12.5rem+gaps — the spec's own documented "fewer columns when narrower" behaviour, confirmed correct)
  • Full 10-page bench comparison: every page's own webengine-side render is byte-identical to round 143's committed one except three (developer.mozilla.org, news.ycombinator.com, tailwindcss.com) — each individually confirmed NOT caused by this change (Chrome's own reference image changing; HN's own live rotating front page; zero reachable columns-* class anywhere in tailwindcss.com's real HTML, despite the utility being defined in its CSS). The real fix sits at ~75,000px on pkg.go.dev's ~77,000px page, outside any bench comparison region ever capped to a page's own top ~2,500px — see the FIDELITY.md entry for the full, page-by-page attribution

🤖 Generated with Claude Code

pkg.go.dev's own "Source Files" list (.UnitFiles-fileList{columns:
12.5rem 5;...}) rendered as one long single column instead of up to 5
narrow ones. Round 143 found this real but deferred it as too large;
this round re-read the actual spec mechanics before trusting that
estimate and found the one confirmed trigger narrower than it looked:
a <ul> of short, uniform-height <li>s, not arbitrary flowing prose.

Scoped to exactly that shape: each in-flow element child is laid out
once at the real column width (fully atomic, never split across
columns), then a single greedy cumulative-height pass buckets children
into N columns and repositions each already-correct box with the
existing translateBox helper. No true iterative balancing, no
column-span, no column-rule painting, no fragmenting a child's own
internal content across columns.

Reuses the existing column-gap field shared with flex/grid rather than
adding a new one (real CSS defines it once, in Box Alignment); its
shared "unset" default moved from the Go zero value to Length{Auto:
true} so multicol can resolve it differently (1em, not flex/grid's 0)
without touching their own behaviour, confirmed via their own gapLen/
gapPxCSS treating Auto and explicit-0 identically.

Caught along the way: two hand-built fallback css.Style{} literals
(place()'s and table.go's cellStyle()) never set ColumnWidth, so its Go
zero value read as "column-width:0" to any code checking !Auto,
breaking five pre-existing Shadow DOM slot tests. Fixed both to set it
explicitly, matching every other Auto-defaulted field in those same
literals.

Bench: every page's own webengine-side render is byte-identical to the
previous round's committed one except three, and none of those three
is this round's own effect (confirmed directly, not assumed) - see the
FIDELITY.md entry for the full attribution. The real fix sits at
~75,000px on pkg.go.dev's ~77,000px page, outside any bench comparison
region ever capped to a page's own top ~2,500px.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit 6c57926 into main Sep 28, 2026
7 checks passed
@tannevaled
tannevaled deleted the multicol-layout branch September 28, 2026 14:39
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