Skip to content

fix(layout): apply a plain inline element's own margin-left/margin-right - #124

Merged
tannevaled merged 1 commit into
mainfrom
round44-inline-margin
Sep 5, 2026
Merged

tannevaled merged 1 commit into
mainfrom
round44-inline-margin

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Summary

InlineItem (the atom of inline layout) carries no margin field of any kind, and nothing in inline collection ever consulted a plain (non-replaced, non-atomic) inline element's own Style.Margin.Left/.Right — its margin was simply never applied.

Confirmed live on news.ycombinator.com: <b class="hnname" style="margin-right:5px">Hacker News</b><a href="newest">new</a> — genuinely no whitespace between the two elements in the source HTML, so the margin-right is the ONLY source of the gap a real browser renders. Without it, the header read "Hacker Newsnew" instead of "Hacker News new".

Changes

  • layout/layout.go: a pendingMargin accumulator on the layouter: entering a plain inline element adds its margin-left, leaving one adds its margin-right, and whichever InlineItem is created next takes the accumulated value into its SpaceBefore field via a new takeMargin() method — reusing the same field and line-layout machinery collapsible whitespace already uses rather than adding a parallel code path. Adjacent inline elements' margins correctly add (not collapse, unlike block margins). Reset alongside the whitespace-collapsing state at a genuine break (a promoted block or forced <br>): a stale margin has nothing left on the same line to apply to once one interrupts.
  • layout/inlinemargin_test.go (new): four tests — margin-right, margin-left, adjacent-margins-add, and the block-break drops-pending-margin boundary case — all confirmed to fail via genuine revert-and-rerun before the fix.

A documented, narrower limitation

Surfaced by the test suite itself (not shipped over): a margin lands correctly whenever the margined element is not the very first item on its line, but layoutInline/wrapOneLine/WrapItems deliberately ignore SpaceBefore for a line's first item (so collapsible leading whitespace never creates a phantom indent) — a margin riding in that same field is ignored for the identical reason. A dedicated non-collapsible field would be needed to survive that case too, not attempted here absent any confirmed live page needing it (this engine's real, confirmed use of inline margin is always between two things already sharing a line, as on this page).

Verified live

The header now renders with the correct gap ("Hacker News new" instead of "Hacker Newsnew"). Bench is flat (SSIM 0.559→0.556) — a single 5px header gap is a vanishing fraction of a page whose comparison region is dominated by 30 story-list rows of ordinary font-rasteriser variance, the same pattern this session's other small header/nav-only fixes have shown.

Test plan

  • go build ./... clean
  • go test ./... — zero regressions
  • Coverage floors hold: css 99.5%, layout 100.0%, paint 100.0%, dom 98.1%
  • New tests confirmed to genuinely fail (revert-and-rerun) before the fix, pass after
  • Bench corpus re-run; results committed
  • CI green

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

…ght (#124)

InlineItem (the atom of inline layout) carries no margin field of any
kind, and nothing in inline collection ever consulted a plain (non-
replaced, non-atomic) inline element's own Style.Margin.Left/.Right --
its margin was simply never applied.

Confirmed live on news.ycombinator.com: `<b class="hnname"
style="margin-right:5px">Hacker News</b><a href="newest">new</a>` --
genuinely no whitespace between the two elements in the source HTML, so
the margin-right is the ONLY source of the gap a real browser renders.
Without it, the header read "Hacker Newsnew" instead of "Hacker News
new".

Fixed with a pendingMargin accumulator on the layouter: entering a plain
inline element adds its margin-left, leaving one adds its margin-right,
and whichever InlineItem is created next takes the accumulated value
into its SpaceBefore field via a new takeMargin() method -- reusing the
same field and line-layout machinery collapsible whitespace already
uses rather than adding a parallel code path. Adjacent inline elements'
margins correctly add (not collapse, unlike block margins). Reset
alongside the whitespace-collapsing state at a genuine break (a promoted
block or forced <br>): a stale margin has nothing left on the same line
to apply to once one interrupts.

A real, narrower limitation surfaced by the session's own regression
test, documented rather than shipped over: a margin lands correctly
whenever the margined element is not the very first item on its line,
but layoutInline/wrapOneLine/WrapItems deliberately ignore SpaceBefore
for a line's first item (so collapsible leading whitespace never creates
a phantom indent) -- a margin riding in that same field is ignored for
the identical reason. A dedicated non-collapsible field would be needed
to survive that case too, not attempted here absent any confirmed live
page needing it.

Verified live: the header now renders with the correct gap. Bench is
flat (0.559->0.556 SSIM) -- a single 5px header gap is a vanishing
fraction of a page whose comparison region is dominated by 30 story-list
rows of ordinary font-rasteriser variance, the same pattern this
session's other small header/nav-only fixes have shown.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit 8144075 into main Sep 5, 2026
7 checks passed
@tannevaled
tannevaled deleted the round44-inline-margin branch September 5, 2026 15:47
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