Skip to content

Fix oversized transcript–composer gap when a task panel is showing#138

Open
yaacovcorcos wants to merge 3 commits into
mainfrom
fix/composer-transcript-gap-20260726
Open

Fix oversized transcript–composer gap when a task panel is showing#138
yaacovcorcos wants to merge 3 commits into
mainfrom
fix/composer-transcript-gap-20260726

Conversation

@yaacovcorcos

@yaacovcorcos yaacovcorcos commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Problem

When a task/stacked panel is showing above the composer (active task list, live-changes header, queued follow-ups), an oversized gap opens up between the last message in the transcript and the composer. Without a task panel the gap is correct; with one, the transcript "hangs" far above the panel.

Root cause — a double space reservation

The composer is a flex sibling below the transcript, not an absolute overlay. So the stacked composer chrome already reserves its own vertical space through the flex layout (the transcript viewport shrinks from the bottom as the chrome grows).

On top of that, the transcript also fed the same chrome height into its in-list bottom spacer:

bottomContentInsetPx={
  composerStackedChromeHeight > 0 ? composerStackedChromeHeight + 8 : undefined
}

So the chrome height was reserved twice — once by flex, once by the spacer — producing a gap that grows with the task panel. The no-task path (spacer at its 64px baseline) was already correct.

This is inherited from Synara (our upstream); it is not present in the T3 ancestor, which uses an absolute-overlay composer + contentInsetEndAdjustment and a fixed footer. Removing the second reservation realigns Scient's flex-sibling layout to a single reservation.

Fix

Make the layout do a single space reservation:

  • Remove the composerStackedChromeHeight-driven feed, so the transcript's bottom spacer stays at its fixed 64px baseline in every state. The task-present gap now equals the perfect no-task gap.
  • Retire the now-vestigial bottomContentInsetPx prop chain (ChatView → ChatTranscriptPane → MessagesTimeline / AgentActivityDetailView) rather than leaving a dead, always-undefined prop threaded through four files. Both consumers collapse to their constant baseline; the MIN_-prefixed constants are renamed since nothing overrides them anymore.
  • The agent-activity detail view was double-counting the same way and is fixed by the same collapse.
  • Correct the misleading "the composer overlaps the transcript, so the transcript reserves matching bottom space" comments, which described an overlay model that isn't how this layout actually works.

composerStackedChromeHeight is kept — it still drives the scroll-compensation layout effect that keeps the transcript pinned to its end as the flex viewport shrinks while the chrome grows. With the double-count gone, that compensation now lands exactly at the bottom.

Behavior

  • Task-present gap == no-task gap (single flex reservation).
  • No change to the no-task case.
  • The production fix is a net deletion (+22 / −29 across 4 files); no new state, no new mechanism. The regression test and its two data-* hooks are additive on top.

Verification

  • oxfmt clean
  • oxlint0 errors (changed files: 0 warnings)
  • tsc --noEmit (web) — clean
  • Affected unit tests — 106 passing (MessagesTimeline, ChatTranscriptPane, AgentActivityDetailView, MessagesTimeline.logic, chat-scroll)
  • Browser tests (chromium): ChatView.browser stable 71 passing, geometry 12 passing

Regression coverage

Added a [geometry:linux] browser test in ChatView.browser.tsx that mounts ChatView with an active task card and again once the turn settles (no card), then asserts the gap between the transcript tail and the composer stack is unchanged. It anchors on the tail spacer's top — the true end of transcript content, past any live-turn activity indicator — so a running turn does not confound the measurement.

Confirmed it is a genuine regression guard (not a tautology) by running it against the pre-fix code:

state pre-fix with fix
task card present gap ≈ 180px (double-counted chrome) → test fails gap ≈ 44px → passes
turn settled (no card) gap ≈ 44px gap ≈ 44px

Two small, purely-additive test hooks were added for stable anchoring: data-chat-composer-stack on the composer block wrapper and data-testid="transcript-bottom-spacer" on the list tail spacer.

🤖 Generated with Claude Code

The composer is a flex sibling below the transcript, so the stacked composer
chrome (live-changes header, active task list, queued follow-ups) already
reserves its own vertical space through the flex layout. The transcript then
fed that same chrome height into its in-list bottom spacer
(bottomContentInsetPx = composerStackedChromeHeight + 8), reserving the space a
second time. The result was a large, growing gap between the last message and
the composer whenever a task/stacked panel was present, while the no-task case
(spacer at its 64px baseline) already looked correct.

Remove the double reservation: the transcript's bottom spacer is now a fixed
64px in every state, matching the perfect no-task gap. This also retires the
now-vestigial bottomContentInsetPx prop chain (ChatView → ChatTranscriptPane →
MessagesTimeline / AgentActivityDetailView), collapsing both consumers to their
constant baseline and correcting the misleading "composer overlaps / reserves
matching bottom space" comments.

composerStackedChromeHeight is kept for its real remaining job: the
scroll-compensation layout effect that keeps the transcript pinned to its end as
the flex viewport shrinks while the chrome grows.

- Task-present gap now equals the no-task gap (single flex reservation).
- Agent activity detail view uses the same fixed baseline (it was also
  double-counting).
- No behavior change to the no-task case.

Verified: oxfmt, oxlint (0 errors), tsc --noEmit (web), and the affected
component/scroll unit tests (106 passing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions github-actions Bot added size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Jul 26, 2026
yaacovcorcos and others added 2 commits July 26, 2026 19:58
Prove the stacked composer chrome is reserved only once: a
[geometry:linux] browser test mounts ChatView with an active task card
and again once the turn settles (no card), then asserts the gap between
the transcript tail and the composer stack is unchanged. It anchors on
the tail spacer's top (the true end of transcript content, past any
live-turn activity indicator) so a running turn does not confound the
measurement.

Verified the test fails on the pre-fix code — the gap balloons to
~180px when the task card appears — and passes on the fix (~44px in
both states).

Adds two stable test hooks: data-chat-composer-stack on the composer
block wrapper and data-testid="transcript-bottom-spacer" on the list
tail spacer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant