fix(web): assert the wrap-width premise instead of reserving a gutter

Review flagged that "equal by construction" rested on an engine behaviour I
had not measured: `scrollbar-gutter: stable` only equalizes the layers if the
engine applies it to `overflow: hidden` the way it does to `overflow-y: auto`.
Measured it on the running app across the three engines Playwright ships,
and the property does not hold up.

  engine    .input / .backdrop / .mirror wrap width
  chromium  776 / 776 / 776   (768 / 768 / 768 with the declaration)
  firefox   776 / 776 / 776   (unchanged by it — overlay scrollbar)
  WebKit    768 / 776 / 776   (unchanged by it)

WebKit reserves for `overflow-y: auto` and not for `overflow: hidden`, so the
declaration left .input at 768 against 776 — exactly the gap it was meant to
close — on the one engine where that gap is observable at all, while costing
every chromium user 8px of text column unconditionally. Reverted: the
composer's metrics are now the same as before this PR.

The WebKit gap predates this change and is not closed here. It is recorded in
the Agent Note with the numbers, and the browser scenario asserts the equality
on the lane's engine so a regression into that state fails loudly. The mirror
is unaffected on WebKit for the drafts measured — the extents still agree — but
a draft whose wrapping turns on those 8px would clamp it.

The review's monotonicity concern resolves the same way: the declaration was
never worse than master, because WebKit already measured 768 against 776
without it. It simply was not better.

Also from this round: the wrap-width assertion now covers .mirror as well as
the two glyph layers — it is the height authority, so a mirror alone wrapping
wider would measure the box short and clip content below the 14-line cap with
every other assertion green. Plus `renderGeometry`'s missing `@param
trailingNewline`, and both e2e tsconfig lists restored to alphabetical order.
This commit is contained in:
creatixchu
2026-07-31 13:13:50 +08:00
parent 2143361195
commit 23c4745560
8 changed files with 86 additions and 63 deletions

View File

@@ -192,20 +192,18 @@
white-space: pre-wrap;
word-break: break-word;
overflow-wrap: anywhere;
/* Equal wrap width on every platform, which is what lets one scroll offset
be mirrored between the layers (InputBar's `scroll` listener). Only .input
scrolls, so only .input would otherwise lose width to a space-consuming
scrollbar — the kind Windows and Firefox draw, and the kind the theme's
`::-webkit-scrollbar` width makes chromium treat as occupying layout space
(see ui-theme styles/scrollbar.css and the sidebar-scrollbar scenario).
A narrower .input wraps a long soft-wrapped draft onto MORE lines than the
backdrop, so it grows taller, its scroll maximum exceeds the backdrop's,
and the mirrored offset clamps below the caret — the same failure the
trailing-line sentinel fixes, in the same direction. Reserving the gutter
on all three keeps the widths identical whatever that platform's
scrollbar costs; `overflow: hidden` is still a scroll container, so the
non-scrolling layers honour it. Measured: 8px reserved on each. */
scrollbar-gutter: stable;
/* These three MUST wrap at one width, because InputBar mirrors a single
scroll offset between .input and .backdrop and a layer that wraps onto
more lines is taller, has a larger scroll maximum, and clamps the mirrored
offset below the caret. Only .input scrolls, so only .input can lose
content width to a scrollbar that consumes layout space.
`scrollbar-gutter: stable` here does NOT buy that guarantee and was
removed after measuring: WebKit applies it to overflow-y:auto but not to
the overflow:hidden layers, so it left .input at 768 against 776 — the
same gap it was meant to close — while costing chromium 8px of text width
unconditionally. The gap it would have closed is measured and recorded in
the Agent Note (2026-07-31-composer-glyph-layer-tracks-the-textarea);
closing it needs one geometry every engine agrees on, not this property. */
}
/* figma 34:10434: #ADB2B8 light / #81858C dark — the caption pair exactly. */