fix(web): keep the input card in place across view tabs

The composer seat is one node laid out against two different edges: in Chat it
is a sticky child of the column's scroller and rides its content box, which a
space-consuming scrollbar shortens, while a view declaring a composer overlay
gets an absolutely positioned seat against the padding box, which no bar
reduces. With the transcript scrolling the two tabs disagreed by the bar's
width, so the centred input card moved 4px sideways on every switch — and the
same 4px inside Chat when a growing transcript began to scroll.

The column now reserves its scrollbar gutter unconditionally and states the
overlay branch a scroll container on the same axes, so both states measure
against the same width.
This commit is contained in:
creatixchu
2026-08-04 16:13:52 +08:00
parent bcf595f41c
commit d907c8475f
11 changed files with 558 additions and 5 deletions
@@ -182,6 +182,17 @@
flex-direction: column;
min-height: 0;
overflow-y: auto;
/* One reserved gutter for every view, because the composer seat takes its
horizontal geometry from two different edges: in Chat it is a sticky CHILD
of this scroller and so rides the content box, which the scrollbar
shortens, while under a view's composer overlay it is positioned against
the padding box, which the scrollbar never reduces. `auto` would reserve
only while this box overflows, which is precisely the difference between
the two states, so the input card would move sideways by the bar's width
on every tab switch — and inside Chat alone the moment a growing
transcript starts to scroll. `stable` makes both edges the same edge at
every content height. */
scrollbar-gutter: stable;
}
.root[data-phase='active'] .viewArea {
@@ -212,7 +223,16 @@
ownership of the seat geometry and its active-phase precedence. */
.scrollBody:has([data-conversation-composer-overlay]) {
position: relative;
overflow: hidden;
/* Still a box nothing scrolls out of — the view fills it and the seat is out
of flow — but declared as a scroll container on the same axes as the Chat
state instead of `overflow: hidden`, so the reservation above reaches this
state too: WebKit honours `scrollbar-gutter` on an `overflow-y: auto` box
and ignores it on a hidden one (measured for the composer's own layers,
see InputBar.module.css). The horizontal axis is declared rather than left
to compute, because a box that scrolls on one axis computes `visible` on
the other to `auto` and would grow a horizontal bar of its own. */
overflow-x: hidden;
overflow-y: auto;
}
.scrollBody:has([data-conversation-composer-overlay]) > .viewArea {