Skip to content

css+layout: implement word-break:break-all and overflow-wrap:break-word - #243

Merged
tannevaled merged 1 commit into
mainfrom
word-break-overflow-wrap
Sep 28, 2026
Merged

tannevaled merged 1 commit into
mainfrom
word-break-overflow-wrap

Conversation

@tannevaled

Copy link
Copy Markdown
Contributor

Summary

  • word-break/overflow-wrap were entirely unimplemented: an unbreakable "word" wider than its own line always overflowed its container instead of wrapping. Confirmed on three independent real pages: pkg.go.dev's own file/directory listings (word-break:break-all and :break-word), news.ycombinator.com's story headline links (word-break:break-word), and developer.mozilla.org, which sets overflow-wrap:break-word on html itself — inherited page-wide.
  • Both properties (and word-break:break-word's deprecated alias) OR into one css.Style.BreaksOverlongWords() check, kept as two separate fields (WordBreakAll, OverflowWrapAnywhere) rather than one shared boolean — collapsing them at parse time would let an unrelated word-break:normal on a later declaration silently cancel an independent, earlier overflow-wrap:break-word, which real CSS's cascade would not do.
  • layout/layout.go's new splitOverlongWord tries splitting the leading overlong item at a rune boundary right before forceOne's own unconditional overflow, on exactly the item forceOne would otherwise place whole.
  • Scoped to exactly the one evidenced effect (avoid overflow) — NOT break-all's stronger "break eagerly even when a word already fits" behaviour, overflow-wrap:anywhere's distinct intrinsic-sizing effect, word-break:keep-all (no CJK line-breaking to suppress), or splitting within a glued run (152,3<sup>†</sup>-shaped) — none of which have a confirmed real-world trigger here.
  • Also documents columns/column-count in FIDELITY.md's Known Gaps (found the same round via the same CSS-property audit, real trigger on pkg.go.dev's own file list, but a genuinely new multi-column layout algorithm — deferred to its own round) and a pre-existing, unrelated forceOne/glueRun inconsistency found while testing this fix (an overflowing glued run splits at its own glue seam; not fixed here, no confirmed real-world trigger for that specific combination).

Test plan

  • go build ./..., go vet ./..., go test ./... all green
  • Coverage floors held (layout back to its 100.0% ratchet floor — an initial version of splitOverlongWord had unreachable defensive code the floor caught immediately; simplified rather than tested around)
  • New tests: css/parse_test.go (TestApplyWordBreak/TestApplyOverflowWrap/TestBreaksOverlongWords — every value, the deprecated alias, unset, explicit inherit, inheritFrom) and layout/layout_test.go (six tests: the unaffected default, both properties independently reaching the same split, no effect on already-fitting text, inheritance to a descendant, the single-rune-cannot-split edge, and the pre-existing glued-run behaviour)
  • Git-stash-confirmed: reverting css/parse.go/css/value.go/layout/layout.go alone (keeping the new tests) fails to even compile (s.WordBreakAll undefined)
  • Full 10-page bench comparison: developer.mozilla.org — the one page whose trigger applies site-wide — shows a real, directly-attributable win (SSIM 0.6028→0.6369, pixdiff 18.10%→13.60%). Every other page flat or within the same live-content noise band already established across many prior rounds (checked directly, not assumed — e.g. pkg.go.dev's own Source Files list has no filename long enough to need splitting on this specific page, its remaining single-column layout being the separate, undeferred columns gap)

🤖 Generated with Claude Code

An unbreakable "word" wider than its own line always overflowed its
container instead of wrapping. Confirmed on three independent real
pages: pkg.go.dev's own file/directory listings (word-break:break-all
and :break-word), news.ycombinator.com's story headline links
(word-break:break-word), and developer.mozilla.org, which sets
overflow-wrap:break-word on <html> itself, inherited page-wide.

Both properties (and word-break:break-word's deprecated alias) OR into
one css.Style.BreaksOverlongWords() check, kept as two separate fields
rather than one shared boolean so an unrelated word-break:normal can
never silently cancel an independent overflow-wrap:break-word from a
different declaration. layoutInline's splitOverlongWord tries splitting
the leading overlong item at a rune boundary right before forceOne's
own unconditional overflow, on exactly the item forceOne would
otherwise place whole.

Scoped to exactly the one evidenced effect (avoid overflow), not
break-all's stronger "break eagerly" behaviour, overflow-wrap:anywhere's
intrinsic-sizing effect, word-break:keep-all, or splitting within a
glued run — none of which have a confirmed real-world trigger here.

Also documents columns/column-count (found the same round, real trigger
on pkg.go.dev's own file list, but a genuinely new multi-column layout
algorithm — deferred to its own round) and a pre-existing, unrelated
forceOne/glueRun inconsistency found while testing this (an overflowing
glued run splits at its glue seam; not fixed, no confirmed trigger).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@tannevaled
tannevaled merged commit 883b72f into main Sep 28, 2026
7 checks passed
@tannevaled
tannevaled deleted the word-break-overflow-wrap branch September 28, 2026 13:28
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