refactor(chat): share composer, scroll follow and jump control across both chat surfaces

The rectification session rendered its own textarea (no length ceiling,
no count), its own rAF scroll follow with a sticky-scroll helper, and its
own jump chip styled differently from the main chat's inline Tailwind
button, while the main chat re-ran scrollTo on every streamed token and
added a smooth scroll on settle. ChatComposer now accepts a controlled
value so the rectification surface reuses it without touching the main
draft store; useConversationScrollAnchor owns the follow through a
ResizeObserver, one frame per content change, for both surfaces; a
single JumpToLatestButton replaces both chips; the 720px transcript
override is gone. DESIGN.md records the shared composer, the jump
control, the reading width and the two shadow tokens.

BUG-477 BUG-478

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
This commit is contained in:
Jesse_Chen
2026-09-02 04:29:01 +00:00
co-authored by Claude Fable 5.1
parent eeb82dfdc4
commit f542c02490
17 changed files with 329 additions and 264 deletions
+4 -1
View File
@@ -152,7 +152,10 @@ test("nothing sits between the transcript and the composer to shift height while
pageSource.indexOf("<ChatComposer"));
assert.doesNotMatch(composerWrap, /composer-suggestions/);
assert.match(composerWrap, /pointer-events-none absolute inset-x-0 bottom-full/);
// Former lock: the inline `pointer-events-none absolute inset-x-0 bottom-full` wrapper. The
// control is now the shared JumpToLatestButton, positioned by `.jump-to-latest` (BUG-478).
assert.match(composerWrap, /<JumpToLatestButton/);
assert.match(globalStyles, /\.jump-to-latest \{[^}]*position: absolute[^}]*bottom: 100%/);
});
test("docks the composer inside the chat panel instead of floating over content", () => {