storytellurx/reports/transcript-mask.md

4.8 KiB

Transcript mask and composer layering

The prior stable implementation was committed as 57f7dd9 before this work. The reference is the user's supplied Samantha69 baseline markup: a masked transcript behind a transparent composer, transparent side suggestion cards, and an opaque enlarged center card.

Layout change

The composer remains a normal-flow sibling of MessageScroller. Its natural height continues to determine the available transcript space. The REUI button remains outside the mask, 16px above the composer boundary.

- scroller: { overflow: "hidden" }
- viewport: { height: "100%" }
- transcript: { paddingBottom: 16 }
+ scroller: { overflow: "visible" }
+ viewport: {
+   height: `calc(100% + ${chatLayoutVars.composerBleed})`,
+   flexShrink: 0,
+   maskImage: /* gradient selected by native data-scrollable attributes */,
+ }
+ transcript: {
+   paddingBottom: `calc(16px + ${chatLayoutVars.composerBleed})`,
+ }

The same overlap is added to viewport height and content bottom inset. This preserves native end clearance while letting text paint beneath the composer's upper strip. The overlap is a paint distance, not an assumed footer height. Each persona retains its prior content inset in addition to that distance.

A shared StyleX variable defaults to 32px for the input-only footer. The scroller receives a 64px theme when suggestions are displayed. There is no footer ResizeObserver, scrollTop correction, duplicated transcript, absolute composer, or package patch.

Samantha's ordinary suggestion background is transparent again. Her existing centered-card style supplies its secondary background and 1.25 scale. The suggestion animation gutters remain inside Base UI ScrollArea.

Mask behavior

The viewport receives the mask, so the jump control, input, suggestions, and focus indicators are not masked. Native data-scrollable attributes select top and bottom fades. At the live edge, fading starts below the readable transcript area; the final message remains above the composer. Away from the live edge, the bottom gradient starts earlier and continues through the upper composer strip. CSS masks alter painting rather than layout; see MDN's mask guide.

This restores the layered treatment without copying the baseline's full absolute-footer layout, shrinking shell, collapsing status header, or fixed suggestion-dependent transcript padding.

Generation scrollbar diagnosis

An initial unconditional 64px overlap was larger than the input-only footer. The Nelly phone fixture measured outer-shell clientHeight=844, scrollHeight=852. Focusing the transcript for keyboard reading scrolled that outer shell to 8px. Suggestions then enlarged the footer, removed the outer overflow, and clamped its scrollTop back to zero. That caused both the unexpected scrollbar and an 8px whole-view jump, even though the transcript's own scrollTop stayed fixed.

The bounded 32px/64px overlap addresses that cause. It does not hide the outer scrollbar to disguise overflow. Per-stage regression assertions check outer-shell overflow and scrollTop through text, suggestions, and completion.

Verification

  • public-transcript-mask.spec.ts: six persona/viewport cases; overlap and end clearance, transparent side cards, unchanged geometry when the mask is toggled, clickable suggestions, and neutral screenshots.
  • public-turn-anchor.spec.ts: streamed fixture barriers; scroll ownership and outer-shell overflow at each stage; manual reading stays fixed when suggestions arrive and the turn completes.
  • public-receipt-close.spec.ts: settled frame positions while receipts close.
  • public-jump-button.spec.ts and public-suggestions-scroll.spec.ts: existing REUI spacing and unclipped scaling.

No live model calls or deployment are used in verification. Full reference pixel parity is not claimed: the user explicitly retained the newer REUI scrolling and unscaled shell.

Latest bounded verification: 4/4 phone cases passed (two streamed conversations and two receipt-close variants, 15.1s). Their 24 recorded streaming-stage snapshots all measured outer-shell scrollHeight - clientHeight = 0 and scrollTop = 0; Nelly and Polaire manual reading remained stable after suggestions and completion. See the recorded scrollbar ownership evidence.

The broader 12-case viewport rerun was stopped when new browser contexts could not hydrate and the textarea remained SSR-disabled, concurrent with external dependency changes. It is not a passing all-width result. Explicit shell overflow/origin assertions were added for future runs; the successful phone capture already records those same invariants. The separate short receipt fixture selector now uses whitespace-normalized text matching because Typographe inserts a nonbreaking widow space before “window.”