From a7b7066267d35da4c3051b8b3eb30d6f59d90f76 Mon Sep 17 00:00:00 2001 From: creatixchu Date: Fri, 31 Jul 2026 11:53:04 +0800 Subject: [PATCH] fix(web): scroll the composer's glyph layer with its textarea MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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`. --- ...-glyph-layer-tracks-the-textarea.i18n.yaml | 6 + ...omposer-glyph-layer-tracks-the-textarea.md | 53 ++++ ...oser-glyph-layer-tracks-the-textarea.zh.md | 53 ++++ apps/web/tests/composer-draft-scroll.e2e.ts | 278 ++++++++++++++++++ .../geometry.expected.md | 17 ++ apps/web/tsconfig.json | 3 +- .../src/client/skeleton/InputBar.tsx | 43 ++- .../ui-conversation/tests/input-bar.spec.tsx | 16 + tsconfig.host.json | 1 + 9 files changed, 461 insertions(+), 9 deletions(-) create mode 100644 .agents/notes/implemented/bug-fix/2026-07-31-composer-glyph-layer-tracks-the-textarea.i18n.yaml create mode 100644 .agents/notes/implemented/bug-fix/2026-07-31-composer-glyph-layer-tracks-the-textarea.md create mode 100644 .agents/notes/implemented/bug-fix/2026-07-31-composer-glyph-layer-tracks-the-textarea.zh.md create mode 100644 apps/web/tests/composer-draft-scroll.e2e.ts create mode 100644 apps/web/tests/snapshots/composer-draft-scroll/geometry.expected.md diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-composer-glyph-layer-tracks-the-textarea.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-07-31-composer-glyph-layer-tracks-the-textarea.i18n.yaml new file mode 100644 index 0000000000..15e96b5788 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-07-31-composer-glyph-layer-tracks-the-textarea.i18n.yaml @@ -0,0 +1,6 @@ +# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each +# side as of the last confirmed-consistent state. Both languages carry equal authority; +# after editing either side, bring the other along and re-record with: +# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-07-31-composer-glyph-layer-tracks-the-textarea.md +2026-07-31-composer-glyph-layer-tracks-the-textarea.md: b9249d59f12cd7bcb2267ce46168e9049517d307 +2026-07-31-composer-glyph-layer-tracks-the-textarea.zh.md: 68d8cc29e30d25fa25f79617e1e7d9e28e4edff8 diff --git a/.agents/notes/implemented/bug-fix/2026-07-31-composer-glyph-layer-tracks-the-textarea.md b/.agents/notes/implemented/bug-fix/2026-07-31-composer-glyph-layer-tracks-the-textarea.md new file mode 100644 index 0000000000..b9249d59f1 --- /dev/null +++ b/.agents/notes/implemented/bug-fix/2026-07-31-composer-glyph-layer-tracks-the-textarea.md @@ -0,0 +1,53 @@ +# Agent Note: The composer's glyph layer tracks the textarea's scroll offset + +Status: implemented + +English | [中文](2026-07-31-composer-glyph-layer-tracks-the-textarea.zh.md) + +## Problem + +A composer draft longer than the 14-line cap could not be scrolled. The caret moved and the selection moved, but the words stayed frozen at line 1 — no wheel gesture, drag, or arrow key brought the end of a long draft on screen, so the bottom of anything past ~14 lines was unreachable and unreadable while writing it. + +The cap itself was working. The composer paints its text in two stacked layers ([InputBar](../../../../packages/client/ui-conversation/src/client/skeleton/InputBar.tsx)): the `