deepseek-harness/apps/web/tests/snapshots/composer-draft-scroll/geometry.expected.md

26 lines
717 B
Markdown
Raw Normal View History

fix(web): scroll the composer's glyph layer with its textarea A composer draft past the 14-line cap could not be scrolled: the caret and the selection moved, but the words stayed frozen at line 1, so the tail of anything longer than the cap was unreachable while writing it. The composer paints its text in two stacked layers. The textarea owns the value, the selection and the caret but renders its own glyphs transparent; every visible character is painted by the decoration backdrop beneath it, which also carries the claim-token highlight, the chips and the ghost hint. The backdrop is `inset: 0; overflow: hidden` — clipped, not scrolled — and nothing linked its offset to the textarea's. Below the cap both layers rest at 0, which is why the defect hid behind every short-draft screenshot and fixture. InputBar now mirrors the textarea's scrollTop onto the backdrop, from a `scroll` listener (every gesture and every caret-driven scroll) and from a layout effect keyed on the committed draft (an edit reflows both layers without necessarily firing a scroll event). Scrolling is layout, so jsdom cannot show this: the unit spec stubs both offsets and proves the mirroring paths run, while a new browser scenario measures the user-visible fact against the built client with a DOM Range over the backdrop's own text — after a wheel gesture over a 40-line draft the last line is on screen and the first has scrolled out. Confirmed both directions: with the mirroring reverted and the packages rebuilt, the golden reads `last draft line is on screen: false` while `textarea moved: true`.
2026-07-31 11:53:04 +08:00
# Composer draft scrolling (14-line cap, two text layers)
## At the start of the draft
- draft overflows the capped box: true
- visible lines: 14
fix(web): give the backdrop the trailing-line sentinel so the layers share one extent Review caught a real divergence the earlier measurements missed: mirroring an offset is only correct while both layers can reach it, and for a draft ending in a newline the backdrop could not. A textarea reserves a line box for the caret after a final newline. `white-space: pre-wrap` collapses a text node's trailing newline and generates none. So a draft ending in a newline made the backdrop exactly one line shorter than the textarea — measured 628 against 652 — and the mirrored assignment clamped, leaving the glyphs one line behind the caret at the very bottom of the draft. The backdrop now carries the same trailing-line sentinel the mirror div has carried all along: its content is the decoration walk plus one newline. The same pre-wrap collapse absorbs it when the draft does not end in a newline, so it costs no height in the ordinary case, and it supplies the missing line box when it does. Verified in isolation first: a bare pre-wrap div measures 180/180/198 against a textarea's 180/198/216 for zero, one and two trailing newlines, and 180/198/216 with the sentinel. Coverage for the shape that exposed it: the browser scenario asserts the two extents are equal before asserting the glyphs reach the end, observing each layer's maximum by asking for an impossible offset and reading back the clamp rather than computing it from scrollHeight, and the golden records the relation. The unit spec pins the backdrop's text as the draft plus exactly one newline. Removing the sentinel fails both, the e2e with the same 628 against 652. The scrollbar-gutter half of the same review point does not reproduce here: both layers measure clientWidth 776 against a border box of 776 while the draft overflows, so this engine's textarea scrollbar is an overlay and takes no width out of the wrap.
2026-07-31 12:12:54 +08:00
- both layers share one scroll extent: true
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.
2026-07-31 13:13:50 +08:00
- all three layers wrap at one width: true
fix(web): scroll the composer's glyph layer with its textarea A composer draft past the 14-line cap could not be scrolled: the caret and the selection moved, but the words stayed frozen at line 1, so the tail of anything longer than the cap was unreachable while writing it. The composer paints its text in two stacked layers. The textarea owns the value, the selection and the caret but renders its own glyphs transparent; every visible character is painted by the decoration backdrop beneath it, which also carries the claim-token highlight, the chips and the ghost hint. The backdrop is `inset: 0; overflow: hidden` — clipped, not scrolled — and nothing linked its offset to the textarea's. Below the cap both layers rest at 0, which is why the defect hid behind every short-draft screenshot and fixture. InputBar now mirrors the textarea's scrollTop onto the backdrop, from a `scroll` listener (every gesture and every caret-driven scroll) and from a layout effect keyed on the committed draft (an edit reflows both layers without necessarily firing a scroll event). Scrolling is layout, so jsdom cannot show this: the unit spec stubs both offsets and proves the mirroring paths run, while a new browser scenario measures the user-visible fact against the built client with a DOM Range over the backdrop's own text — after a wheel gesture over a 40-line draft the last line is on screen and the first has scrolled out. Confirmed both directions: with the mirroring reverted and the packages rebuilt, the golden reads `last draft line is on screen: false` while `textarea moved: true`.
2026-07-31 11:53:04 +08:00
- textarea scroll offset: 0px
- glyph layer tracks it: true
- first draft line is on screen: true
- last draft line is on screen: false
## Scrolled to the end of the draft
- textarea moved: true
- glyph layer tracks it: true
- first draft line has scrolled out above: true
- last draft line is on screen: true
fix(web): give the backdrop the trailing-line sentinel so the layers share one extent Review caught a real divergence the earlier measurements missed: mirroring an offset is only correct while both layers can reach it, and for a draft ending in a newline the backdrop could not. A textarea reserves a line box for the caret after a final newline. `white-space: pre-wrap` collapses a text node's trailing newline and generates none. So a draft ending in a newline made the backdrop exactly one line shorter than the textarea — measured 628 against 652 — and the mirrored assignment clamped, leaving the glyphs one line behind the caret at the very bottom of the draft. The backdrop now carries the same trailing-line sentinel the mirror div has carried all along: its content is the decoration walk plus one newline. The same pre-wrap collapse absorbs it when the draft does not end in a newline, so it costs no height in the ordinary case, and it supplies the missing line box when it does. Verified in isolation first: a bare pre-wrap div measures 180/180/198 against a textarea's 180/198/216 for zero, one and two trailing newlines, and 180/198/216 with the sentinel. Coverage for the shape that exposed it: the browser scenario asserts the two extents are equal before asserting the glyphs reach the end, observing each layer's maximum by asking for an impossible offset and reading back the clamp rather than computing it from scrollHeight, and the golden records the relation. The unit spec pins the backdrop's text as the draft plus exactly one newline. Removing the sentinel fails both, the e2e with the same 628 against 652. The scrollbar-gutter half of the same review point does not reproduce here: both layers measure clientWidth 776 against a border box of 776 while the draft overflows, so this engine's textarea scrollbar is an overlay and takes no width out of the wrap.
2026-07-31 12:12:54 +08:00
## Draft ending in a newline, scrolled to the end
- both layers share one scroll extent: true
- glyph layer tracks the caret: true
- last draft line is on screen: true