What happened
When a text-bearing box's content-derived width is materialized as a fixed px value (planWidth fixed, compiler/src/generate/css.ts ~2641), the emission pipeline can quantize it below the captured value. For a box captured at its max-content width there is zero slack, so any downward quantization — however small — tips the text onto a second line the source never had. With the height also materialized and no overflow clip, the overflow line often paints outside the box's background, so a word silently disappears rather than visibly breaking.
The archetype is an absolutely-positioned badge/tooltip/floating label: out-of-flow, shrink-to-fit, single line, materialized because its width can't be re-derived from flow context.
Source (left) vs clone (right) — same font stack, same 12px/700 text; the clone's "popular" wrapped and painted white-on-white below the pill:
| source |
clone |
 |
 |
To reproduce
- Surface: CLI
- URL cloned: locally-served fixture (below) — no external site
- Command:
npm run clone -- http://localhost:8787/ --out=./repro
- Options: defaults
<!DOCTYPE html>
<html lang="en">
<head><meta charset="utf-8"><style>
body { font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; padding: 80px; }
.card { position: relative; width: 280px; border: 1px solid #ddd; border-radius: 12px; padding: 28px; }
.badge { position: absolute; top: -13px; left: 24px; background: #2f6df6; color: #fff;
font-size: 12px; font-weight: 700; padding: 4px 12px; border-radius: 999px; margin: 0; }
</style></head>
<body><div class="card"><p class="badge">Most popular</p><h3>Agency</h3><p>$129/mo</p></div></body>
</html>
On macOS / Chromium headless shell 141 the badge's captured cs.width is 75.328125px (content-box; in a box-sizing:border-box variant it's 99.328125px border-box — same mechanism). Generated page.tsx emits w-[75.3px] — 0.028px narrower than the text's max-content width — and the clone renders two line boxes ("Most" / "popular"; verified via Range.getClientRects()). The exact fraction is font-metric dependent; the mechanism isn't: any captured fraction that quantizes down reproduces this.
Expected behavior
The badge renders on one line, as captured.
Root cause: three independent down-quantization sites
snapLen (tailwind.ts:39) rounds to the nearest 0.1px — down for ~half of all fractions (75.328 → 75.3).
snapLen's integer branch has a float edge: 99.1 - 99 = 0.0999999999… which is < 0.1, so 99.1px snaps to 99px — a 0.1px round-down even of an already one-decimal value.
snapBase (tailwind.ts:602) snaps a width within 0.25px of a named scale step onto the step (96.2px → w-24 = 96px) — after prettifyBase, so it can undo any earlier upward correction.
The percent-width path already guards exactly this cliff — css.ts:1710 ("a nowrap pill … has NO slack: rounding the % DOWN emits a width below its intrinsic content") — and in-flow leaves get white-space:nowrap via nowrapWrapVulnerable (css.ts:3141). But that inference needs the sizing probe's wMin/wMax, and the probe is limited to static/relative nodes (walker.ts:367), so out-of-flow text boxes fall through every existing defense: no probe evidence → no width:auto recovery, no nowrap, and a materialized px width that quantizes with no slack.
Related paths with the same exposure (not demonstrated, from code reading): mixedFillByVp regimes that store raw cs.width px inside a percentVp plan (css.ts:1759 → emitted at css.ts:2632); generic flex-basis/max-width arbitrary lengths (css.ts:106,118); text-bearing pseudo-elements (css.ts:3322). The basis plan variant would also qualify but appears to have no producer at present.
Fix directions (we prototyped one; happy to PR whichever you prefer)
- A. Extend the sizing probe to out-of-flow text boxes — probe
width:auto on absolute/fixed leaves like the in-flow probe does, and when the browser proves auto reproduces the captured box, drop the materialized width entirely. Follows the existing browser-as-oracle sizingVerdict design, restores genuine shrink-to-fit (no quantization at all), and gives nowrapWrapVulnerable the wMin/wMax evidence to add nowrap where the box sits flush. Most principled; most work.
- B. Directional (ceil) quantization for content-tight materialized widths. Prototyped: pre-ceiling
cs.width to 0.1px for text-bearing nodes at the fixed branch fixes the repro (w-[4.7125rem] = 75.4px, one line) and passes the full suite (clone-static 525/525, core 13/13 incl. the Gate 6 determinism golden). But as-is it is not monotone through the pipeline — sites 2 and 3 above can still re-round down — and keying on "has a direct text child" is both under-inclusive (descendant text via a wrapper <span>) and over-inclusive (authored fixed widths don't need widening). A real version needs a monotonicity guarantee through snapLen + snapBase, content-tight evidence, and regression tests for the 99.05 → 99.1 → 99px and 96.11 → 96.2 → w-24 boundary cases.
- C.
white-space:nowrap on captured-single-line out-of-flow text leaves. Empirically fixes the badge even with the too-narrow width (0.03px invisible overflow instead of a wrap), and is robust to font-metric drift across platforms in a way sub-pixel width fixes are not. Needs the same care the in-flow inference applies, and A would supply the intrinsic-width evidence to do it on your existing terms.
Our read: A matches the codebase's design philosophy, with C as a natural byproduct; B alone is a band-aid whose edge cases are worse than they look.
Environment
- OS + Node: macOS 15 (Darwin 25.5.0), node v24.14.1
- Commit / version: ditto.site@0.1.0 at current
main
- Chromium installed: yes (Playwright Chromium headless shell 141)
Logs / artifacts
Measured on the styled (border-box) variant: source badge getBoundingClientRect().width = 99.328125, one line box; clone = 99.296875 (w-[99.3px]), two line boxes ("Most" 28px / "popular" 44px). The snapLen float edge and snapBase down-snap were verified by direct evaluation. Repro is a fabricated local fixture; no external site captured.
What happened
When a text-bearing box's content-derived width is materialized as a fixed px value (planWidth
fixed,compiler/src/generate/css.ts~2641), the emission pipeline can quantize it below the captured value. For a box captured at its max-content width there is zero slack, so any downward quantization — however small — tips the text onto a second line the source never had. With the height also materialized and no overflow clip, the overflow line often paints outside the box's background, so a word silently disappears rather than visibly breaking.The archetype is an absolutely-positioned badge/tooltip/floating label: out-of-flow, shrink-to-fit, single line, materialized because its width can't be re-derived from flow context.
Source (left) vs clone (right) — same font stack, same 12px/700 text; the clone's "popular" wrapped and painted white-on-white below the pill:
To reproduce
npm run clone -- http://localhost:8787/ --out=./reproOn macOS / Chromium headless shell 141 the badge's captured
cs.widthis75.328125px(content-box; in abox-sizing:border-boxvariant it's99.328125pxborder-box — same mechanism). Generatedpage.tsxemitsw-[75.3px]— 0.028px narrower than the text's max-content width — and the clone renders two line boxes ("Most" / "popular"; verified viaRange.getClientRects()). The exact fraction is font-metric dependent; the mechanism isn't: any captured fraction that quantizes down reproduces this.Expected behavior
The badge renders on one line, as captured.
Root cause: three independent down-quantization sites
snapLen(tailwind.ts:39) rounds to the nearest 0.1px — down for ~half of all fractions (75.328 → 75.3).snapLen's integer branch has a float edge:99.1 - 99 = 0.0999999999…which is< 0.1, so99.1pxsnaps to99px— a 0.1px round-down even of an already one-decimal value.snapBase(tailwind.ts:602) snaps a width within 0.25px of a named scale step onto the step (96.2px → w-24= 96px) — afterprettifyBase, so it can undo any earlier upward correction.The percent-width path already guards exactly this cliff —
css.ts:1710("a nowrap pill … has NO slack: rounding the % DOWN emits a width below its intrinsic content") — and in-flow leaves getwhite-space:nowrapvianowrapWrapVulnerable(css.ts:3141). But that inference needs the sizing probe'swMin/wMax, and the probe is limited to static/relative nodes (walker.ts:367), so out-of-flow text boxes fall through every existing defense: no probe evidence → nowidth:autorecovery, no nowrap, and a materialized px width that quantizes with no slack.Related paths with the same exposure (not demonstrated, from code reading):
mixedFillByVpregimes that store rawcs.widthpx inside apercentVpplan (css.ts:1759→ emitted atcss.ts:2632); genericflex-basis/max-widtharbitrary lengths (css.ts:106,118); text-bearing pseudo-elements (css.ts:3322). Thebasisplan variant would also qualify but appears to have no producer at present.Fix directions (we prototyped one; happy to PR whichever you prefer)
width:autoon absolute/fixed leaves like the in-flow probe does, and when the browser proves auto reproduces the captured box, drop the materialized width entirely. Follows the existing browser-as-oraclesizingVerdictdesign, restores genuine shrink-to-fit (no quantization at all), and givesnowrapWrapVulnerablethewMin/wMaxevidence to add nowrap where the box sits flush. Most principled; most work.cs.widthto 0.1px for text-bearing nodes at thefixedbranch fixes the repro (w-[4.7125rem]= 75.4px, one line) and passes the full suite (clone-static 525/525, core 13/13 incl. the Gate 6 determinism golden). But as-is it is not monotone through the pipeline — sites 2 and 3 above can still re-round down — and keying on "has a direct text child" is both under-inclusive (descendant text via a wrapper<span>) and over-inclusive (authored fixed widths don't need widening). A real version needs a monotonicity guarantee throughsnapLen+snapBase, content-tight evidence, and regression tests for the99.05 → 99.1 → 99pxand96.11 → 96.2 → w-24boundary cases.white-space:nowrapon captured-single-line out-of-flow text leaves. Empirically fixes the badge even with the too-narrow width (0.03px invisible overflow instead of a wrap), and is robust to font-metric drift across platforms in a way sub-pixel width fixes are not. Needs the same care the in-flow inference applies, and A would supply the intrinsic-width evidence to do it on your existing terms.Our read: A matches the codebase's design philosophy, with C as a natural byproduct; B alone is a band-aid whose edge cases are worse than they look.
Environment
mainLogs / artifacts
Measured on the styled (border-box) variant: source badge
getBoundingClientRect().width= 99.328125, one line box; clone = 99.296875 (w-[99.3px]), two line boxes ("Most" 28px / "popular" 44px). The snapLen float edge and snapBase down-snap were verified by direct evaluation. Repro is a fabricated local fixture; no external site captured.