mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix(web): reclaim overlay scrollbar gutter
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# 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-08-04-composer-tab-gutter-reservation.md
|
||||
2026-08-04-composer-tab-gutter-reservation.md: 3b28c35c1f11676e41cabde76d1b0d16c688f034
|
||||
2026-08-04-composer-tab-gutter-reservation.zh.md: c357dd06c52a834c18d2e8a25246d4ad003db548
|
||||
2026-08-04-composer-tab-gutter-reservation.md: 2c2813371af073eee4bd80ce7813e77af3c0c319
|
||||
2026-08-04-composer-tab-gutter-reservation.zh.md: e450def2482218028875a48fce8842c88b5ba82e
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Status: implemented
|
||||
|
||||
> Superseded for overlay views by [the seat-width compensation](2026-08-12-composer-overlay-seat-width-compensation.md): the overlay branch no longer reserves a gutter, and the seat compensates for the bar instead. Chat keeps the reservation described here.
|
||||
|
||||
English | [中文](2026-08-04-composer-tab-gutter-reservation.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
Status: implemented
|
||||
|
||||
> 对覆盖视图已被 [座位宽度补偿](2026-08-12-composer-overlay-seat-width-compensation.md) 取代:覆盖分支不再预留滚动条槽,座位改为补偿滚动条宽度。Chat 仍保留本文所述的预留。
|
||||
|
||||
[English](2026-08-04-composer-tab-gutter-reservation.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
@@ -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-08-12-composer-overlay-seat-width-compensation.md
|
||||
2026-08-12-composer-overlay-seat-width-compensation.md: 85281ed4301046672ff4c02320b196a397e6d163
|
||||
2026-08-12-composer-overlay-seat-width-compensation.zh.md: 7bcf060e651a828068ef207caaebd01fa2ffd7ae
|
||||
@@ -0,0 +1,36 @@
|
||||
# Agent Note: The overlay composer seat compensates for the bar instead of reserving a gutter
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-08-12-composer-overlay-seat-width-compensation.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
The [composer-tab gutter reservation](2026-08-04-composer-tab-gutter-reservation.md) made the column's scroller reserve a scrollbar gutter unconditionally, so the composer seat measured the same width in Chat and in a view with a composer overlay. The cost was paid by every overlay view: the view's content column ended 8px short of the column's right edge, because the scroller reserved a gutter for a bar it never draws — the trajectory ledger owns its own scrollers and the outer box never scrolls.
|
||||
|
||||
The trajectory table made that cost visible: its full-width row divider lines stopped 8px short of the pane edge, leaving a strip of whitespace at the right of every line and of the whole content column.
|
||||
|
||||
## Decision
|
||||
|
||||
The reservation now belongs to Chat alone. The overlay branch declares `scrollbar-gutter: auto`, so the view's content spans the full column; the overlay composer seat (absolutely positioned against the padding box) gives back the bar's width with `right: var(--dsh-scrollbar-width)`, so the input card still measures the same width as Chat's seat and does not move between tabs.
|
||||
|
||||
The compensation value is not a literal: ui-theme's scrollbar.css defines `--dsh-scrollbar-width` (8px on the WebKit path) beside the `::-webkit-scrollbar` rule it mirrors, and the seat reads that variable. A change to the sheet's bar width reaches the compensation in the same reviewable diff as the bar itself.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep the unconditional reservation and shrink every overlay view.** The pre-fix behavior. It keeps one declaration for both tabs but taxes every overlay view with an 8px content column, which the trajectory ledger surfaced as visible whitespace. Rejected because the overlay views own their scrolling; they should not pay for Chat's bar.
|
||||
|
||||
**Reserve on the overlay branch too and let the view bleed into the gutter.** More moving parts for the same result: the gutter would still exist on a box that never scrolls, and the view would have to break out of the content box to reclaim its width.
|
||||
|
||||
**Accept the 4px card shift.** Dropping the reservation without compensating the seat would move the input card on every tab switch, which is exactly the symptom the earlier note fixed. Rejected: the card position is a deliberate cross-tab invariant.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Chat keeps its reserved gutter and its stable card position; nothing changes on that tab.
|
||||
- Overlay views (trajectory) span the full column; the trajectory ledger's divider lines reach the pane edge.
|
||||
- The input card still holds one horizontal position across the Chat and Trajectory tabs, now by two mechanisms instead of one: Chat reserves, the overlay seat compensates.
|
||||
- `--dsh-scrollbar-width` becomes a public ui-theme variable read outside ui-theme; the scrollbar-styles spec's indirection checks only scan `--dsh-scrollbar-thumb{,-hover}` rebinds, so the width variable is not covered by the pair gate.
|
||||
|
||||
## Testing
|
||||
|
||||
`apps/web/tests/composer-tab-geometry.e2e.ts` still asserts the card holds its position across tabs and now also asserts the split: Chat's scroller keeps `scrollbar-gutter: stable` and a nonzero band, while the overlay branch resolves `auto` with a zero band. The control cascade changed with the mechanism: it now drops the seat's `right` compensation (instead of dropping a gutter Chat never had on that branch) and measures the same 4px shift, proving the equal rectangles are not a tab switch that never reached layout. The committed golden records both states.
|
||||
@@ -0,0 +1,36 @@
|
||||
# Agent Note: 覆盖视图的 composer 座位改为补偿滚动条宽度,不再预留滚动条槽
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-08-12-composer-overlay-seat-width-compensation.md) | 中文
|
||||
|
||||
## 问题
|
||||
|
||||
[composer 标签页滚动条槽预留](2026-08-04-composer-tab-gutter-reservation.md) 让会话列滚动容器无条件预留一条滚动条槽,使 composer 座位在 Chat 与带 composer 覆盖的视图中测得相同宽度。代价由每个覆盖视图承担:视图内容列比列右边缘窄 8px,因为滚动容器为一条它从不绘制的滚动条预留了槽——trajectory 台账由视图内部自己的滚动容器滚动,外层盒子从不滚动。
|
||||
|
||||
trajectory 表格让这个代价显形:整行分隔线在面板右边缘前 8px 处停止,每条线右侧以及整个内容列右侧都留下一条空白带。
|
||||
|
||||
## 决策
|
||||
|
||||
预留现在只属于 Chat。覆盖分支声明 `scrollbar-gutter: auto`,视图内容占满整列;覆盖分支的 composer 座位(相对 padding box 绝对定位)用 `right: var(--dsh-scrollbar-width)` 让出滚动条宽度,使输入卡仍与 Chat 座位测得相同宽度,切换标签页时不移动。
|
||||
|
||||
补偿值不是字面量:ui-theme 的 scrollbar.css 在它镜像的 `::-webkit-scrollbar` 规则旁定义 `--dsh-scrollbar-width`(WebKit 路径 8px),座位读取该变量。样式表滚动条宽度一变,补偿就会与滚动条本身出现在同一次可审阅的 diff 中。
|
||||
|
||||
## 备选方案
|
||||
|
||||
**保留无条件预留,压缩每个覆盖视图。** 修复前行为。两个标签页一条声明,但每个覆盖视图都要付出 8px 内容列,trajectory 台账将其显现为可见空白。已拒绝:覆盖视图自己滚动,不应为 Chat 的滚动条买单。
|
||||
|
||||
**覆盖分支也预留,并让视图渗入滚动条槽。** 同样结果下更多活动部件:从不滚动的盒子上仍存在滚动条槽,视图还得突破内容盒才能取回宽度。
|
||||
|
||||
**接受 4px 卡片位移。** 去掉预留却不补偿座位,会在每次切换标签页时移动输入卡——正是前一份 note 修复的症状。已拒绝:卡片位置是刻意保持的跨标签页不变量。
|
||||
|
||||
## 后果
|
||||
|
||||
- Chat 保留滚动条槽与稳定的卡片位置;该标签页无任何变化。
|
||||
- 覆盖视图(trajectory)占满整列;trajectory 台账的分隔线到达面板右边缘。
|
||||
- 输入卡在 Chat 与 Trajectory 标签页间仍保持同一水平位置,现在由两种机制而非一种达成:Chat 预留,覆盖座位补偿。
|
||||
- `--dsh-scrollbar-width` 成为 ui-theme 对外、且被 ui-theme 之外读取的变量;scrollbar-styles 规格的间接层检查只扫描 `--dsh-scrollbar-thumb{,-hover}` 重绑,宽度变量不受成对门禁覆盖。
|
||||
|
||||
## 测试
|
||||
|
||||
`apps/web/tests/composer-tab-geometry.e2e.ts` 仍断言输入卡在标签页间保持位置,并新增断言拆分:Chat 滚动容器保持 `scrollbar-gutter: stable` 与非零槽宽,覆盖分支解析为 `auto` 且槽宽为零。控制级联随机制改变:现在移除座位的 `right` 补偿(而非移除该分支上 Chat 从未有过的槽),测得同样的 4px 位移,证明相等的矩形并非从未到达布局的标签页切换。提交的 golden 记录两种状态。
|
||||
@@ -11,12 +11,13 @@
|
||||
// gets an absolutely positioned seat instead, laid out against the padding box,
|
||||
// which the scrollbar never reduces.
|
||||
//
|
||||
// Without a shared reservation the two tabs disagree by exactly the bar's
|
||||
// width for as long as the transcript overflows: the card jumps sideways on
|
||||
// every tab switch, and inside Chat alone at the moment a growing transcript
|
||||
// starts to scroll. The column reserves the gutter unconditionally
|
||||
// (`scrollbar-gutter: stable`) and states the overlay branch as a scroll
|
||||
// container on the same axes, so both edges are the same edge.
|
||||
// The column handles the two edges without reserving the gutter on both: Chat
|
||||
// keeps `scrollbar-gutter: stable` so its seat's content box never jumps as the
|
||||
// transcript starts to scroll; the overlay branch does NOT reserve (the view
|
||||
// owns its own scrollers, so a reserved gutter would only narrow the view's
|
||||
// content by the bar's width), and the overlay seat instead gives back the
|
||||
// bar's width (`right: var(--dsh-scrollbar-width)`) so both seats measure the
|
||||
// same width and the card does not move.
|
||||
//
|
||||
// Only a real engine can show this. The seat's geometry is layout: jsdom gives
|
||||
// every element a zero-sized box and reports no scrollbar at all, so a unit spec
|
||||
@@ -33,12 +34,11 @@
|
||||
// configuration: ui-theme's scrollbar.css gives `::-webkit-scrollbar` a width,
|
||||
// and a bar that occupies layout space is what the product actually draws.
|
||||
//
|
||||
// The scenario runs that unreserved cascade in the page — `scrollbar-gutter: auto`
|
||||
// on the scroller, `overflow: hidden` on the overlay branch — and measures the
|
||||
// same two tabs through it, which is what keeps the equal rectangles above from
|
||||
// being explained by a tab switch that never reached the layout. It is the
|
||||
// reported symptom as a number: the card moves 4px, half the 8px band, on each
|
||||
// edge.
|
||||
// The scenario runs that unreserved cascade in the page — the overlay seat's
|
||||
// `right` compensation dropped to 0 — and measures the same two tabs through
|
||||
// it, which is what keeps the equal rectangles above from being explained by a
|
||||
// tab switch that never reached the layout. It is the reported symptom as a
|
||||
// number: the card moves 4px, half the 8px band, on each edge.
|
||||
//
|
||||
// Zero model calls: a seeded cold session renders from its log, and switching
|
||||
// tabs asks the host for nothing. A stray stream would fail loud with NO_ADAPTER.
|
||||
@@ -62,9 +62,9 @@ const SNAPSHOT_DIR = fileURLToPath(new URL('./snapshots/composer-tab-geometry',
|
||||
* Absolute coordinates are deliberately absent: they depend on the sidebar's
|
||||
* laid-out width and on font metrics, so committing them would produce a fixture
|
||||
* that has to be re-recorded per platform. What is recorded is the distance
|
||||
* between the two tabs' rectangles, which is zero when the reservation holds and
|
||||
* between the two tabs' rectangles, which is zero when the compensation holds and
|
||||
* the bar's width when it does not — including under the control, so the golden
|
||||
* carries the shift the unreserved cascade produces rather than only its absence.
|
||||
* carries the shift the uncompensated cascade produces rather than only its absence.
|
||||
*/
|
||||
const GEOMETRY_EXPECTED = join(SNAPSHOT_DIR, 'geometry.expected.md')
|
||||
const MODE = webSnapshotMode()
|
||||
@@ -114,15 +114,15 @@ async function setMeasuredViewport(
|
||||
}
|
||||
|
||||
/**
|
||||
* The unreserved cascade, injected into the page: the reservation dropped and
|
||||
* the overlay branch forced to a hidden box. `!important` beats the module
|
||||
* The uncompensated cascade, injected into the page: the overlay seat's `right`
|
||||
* compensation dropped to 0, so it measures the full padding box while Chat's
|
||||
* seat still rides the reserved content box. `!important` beats the module
|
||||
* rules without a rebuild, and the id lets the control be lifted again in the
|
||||
* same session.
|
||||
*/
|
||||
const CONTROL_STYLE_ID = 'composer-tab-geometry-control'
|
||||
const CONTROL_CSS = `
|
||||
[data-conversation-scroll] { scrollbar-gutter: auto !important; }
|
||||
[data-conversation-scroll]:has([data-conversation-composer-overlay]) { overflow: hidden !important; }
|
||||
[data-conversation-scroll]:has([data-conversation-composer-overlay]) > [data-composer-seat] { right: 0 !important; }
|
||||
`
|
||||
|
||||
/** The column scroller and the input card as the browser lays them out, in one tab. */
|
||||
@@ -221,9 +221,11 @@ async function compareTabs(page: Page): Promise<TabComparison> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the unreserved cascade in the page for one measurement, then lift it.
|
||||
* Run the uncompensated cascade in the page for one measurement, then lift it:
|
||||
* the overlay seat's `right` compensation dropped to 0, so it measures the
|
||||
* full padding box while Chat's seat still rides the reserved content box.
|
||||
* @param page - the page under test.
|
||||
* @returns the comparison as the column lays out without the reservation.
|
||||
* @returns the comparison as the column lays out without the compensation.
|
||||
*/
|
||||
async function compareTabsWithoutReservation(page: Page): Promise<TabComparison> {
|
||||
await page.evaluate(({ id, css }) => {
|
||||
@@ -268,7 +270,7 @@ async function openSeededSession(page: Page): Promise<void> {
|
||||
* Render the golden body.
|
||||
* @param wide - comparison at the viewport where the card sits at its width cap.
|
||||
* @param narrow - comparison at the viewport where the card shrinks with the column.
|
||||
* @param control - comparison at the wide viewport with the reservation removed.
|
||||
* @param control - comparison at the wide viewport with the compensation removed.
|
||||
* @returns the golden body, without a trailing newline.
|
||||
*/
|
||||
function renderGeometry(wide: TabComparison, narrow: TabComparison, control: TabComparison): string {
|
||||
@@ -291,7 +293,7 @@ function renderGeometry(wide: TabComparison, narrow: TabComparison, control: Tab
|
||||
'',
|
||||
...section(`Wide viewport (${String(WIDE_VIEWPORT.width)}px, card at its cap)`, wide),
|
||||
...section(`Narrow viewport (${String(NARROW_VIEWPORT.width)}px, card shrinking with the column)`, narrow),
|
||||
...section('Wide viewport, reservation removed in the page (control)', control),
|
||||
...section('Wide viewport, seat compensation removed in the page (control)', control),
|
||||
].join('\n').trimEnd()
|
||||
}
|
||||
|
||||
@@ -322,22 +324,25 @@ describe('web e2e: input card position across view tabs', () => {
|
||||
await scaffold?.close()
|
||||
})
|
||||
|
||||
it('reserves the same gutter in both tabs while the transcript scrolls', async () => {
|
||||
it('reserves the gutter in Chat and lets Trajectory own its width', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-composer-tab-geometry-band'))
|
||||
await setMeasuredViewport(page, WIDE_VIEWPORT, false)
|
||||
// Vacuity guard, in two parts. A transcript that does not overflow gives
|
||||
// Chat no scrollbar, and a hidden or overlaid bar gives it no width; either
|
||||
// would make the tabs agree without the reservation doing anything.
|
||||
// would make the tabs agree without the compensation doing anything.
|
||||
await expect.poll(async () => (await measureTab(page)).scrolls, { timeout: 10_000 }).toBe(true)
|
||||
const comparison = await compareTabs(page)
|
||||
expect(comparison.chat.band).toBeGreaterThan(0)
|
||||
// The reservation reaches both states, which is the whole point: the same
|
||||
// band, on a box that scrolls and on one that only holds a view.
|
||||
// Chat keeps the unconditional reservation so its seat's content box never
|
||||
// jumps as the transcript starts to scroll.
|
||||
expect(comparison.chat.gutter).toBe('stable')
|
||||
expect(comparison.trajectory.gutter).toBe('stable')
|
||||
expect(comparison.trajectory.band).toBe(comparison.chat.band)
|
||||
// The overlay branch does NOT reserve: the view owns its own scrollers, so
|
||||
// a reserved gutter would only narrow the view's content by the bar's
|
||||
// width. The seat compensates instead, which the next test asserts.
|
||||
expect(comparison.trajectory.gutter).toBe('auto')
|
||||
expect(comparison.trajectory.band).toBe(0)
|
||||
// Declared as a scroll container on both axes rather than left to compute:
|
||||
// `overflow: hidden` would drop the reservation in WebKit, and a `visible`
|
||||
// `overflow: hidden` would drop any reservation in WebKit, and a `visible`
|
||||
// horizontal axis computes to `auto` beside a scrolling one.
|
||||
expect(comparison.trajectory.overflowY).toBe('auto')
|
||||
expect(comparison.trajectory.overflowX).toBe('hidden')
|
||||
@@ -378,20 +383,21 @@ describe('web e2e: input card position across view tabs', () => {
|
||||
expect(tripwire.pageErrors).toEqual([])
|
||||
}, 60_000)
|
||||
|
||||
it('moves the card again once the reservation is removed in the page', async () => {
|
||||
it('moves the card again once the seat compensation is removed in the page', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-composer-tab-geometry-control'))
|
||||
await setMeasuredViewport(page, WIDE_VIEWPORT, false)
|
||||
// The control: without it, equal rectangles could also mean the tab switch
|
||||
// never reached the layout. Under the unreserved cascade the Chat scroller
|
||||
// keeps its bar and the Trajectory branch becomes a hidden box with none,
|
||||
// and the card moves by half the band on each edge.
|
||||
// never reached the layout. Under the unreserved cascade the overlay seat
|
||||
// loses its `right` compensation and measures the full padding box, so the
|
||||
// card moves by half the band on each edge. Chat's own reservation is
|
||||
// untouched — that is the side that must not change.
|
||||
const comparison = await compareTabsWithoutReservation(page)
|
||||
expect(comparison.chat.gutter).toBe('auto')
|
||||
expect(comparison.chat.gutter).toBe('stable')
|
||||
expect(comparison.chat.band).toBeGreaterThan(0)
|
||||
expect(comparison.trajectory.band).toBe(0)
|
||||
expect(comparison.leftShift).toBe(comparison.chat.band / 2)
|
||||
expect(comparison.rightShift).toBe(comparison.chat.band / 2)
|
||||
// Restoring the sheet restores the reservation, so the control cannot leak
|
||||
// Restoring the sheet restores the compensation, so the control cannot leak
|
||||
// into the remaining measurements.
|
||||
const restored = await compareTabs(page)
|
||||
expect(restored.leftShift).toBe(0)
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
- Chat: scrollbar-gutter stable, overflow hidden/auto
|
||||
- Chat scroller scrolls: true
|
||||
- Chat reserved band: 8px
|
||||
- Trajectory: scrollbar-gutter stable, overflow hidden/auto
|
||||
- Trajectory: scrollbar-gutter auto, overflow hidden/auto
|
||||
- Trajectory scroller scrolls: false
|
||||
- Trajectory reserved band: 8px
|
||||
- Trajectory reserved band: 0px
|
||||
- input card left edge moves between tabs: 0px
|
||||
- input card right edge moves between tabs: 0px
|
||||
- input card width changes between tabs: 0px
|
||||
@@ -17,19 +17,19 @@
|
||||
- Chat: scrollbar-gutter stable, overflow hidden/auto
|
||||
- Chat scroller scrolls: true
|
||||
- Chat reserved band: 8px
|
||||
- Trajectory: scrollbar-gutter stable, overflow hidden/auto
|
||||
- Trajectory: scrollbar-gutter auto, overflow hidden/auto
|
||||
- Trajectory scroller scrolls: false
|
||||
- Trajectory reserved band: 8px
|
||||
- Trajectory reserved band: 0px
|
||||
- input card left edge moves between tabs: 0px
|
||||
- input card right edge moves between tabs: 0px
|
||||
- input card width changes between tabs: 0px
|
||||
|
||||
## Wide viewport, reservation removed in the page (control)
|
||||
## Wide viewport, seat compensation removed in the page (control)
|
||||
|
||||
- Chat: scrollbar-gutter auto, overflow hidden/auto
|
||||
- Chat: scrollbar-gutter stable, overflow hidden/auto
|
||||
- Chat scroller scrolls: true
|
||||
- Chat reserved band: 8px
|
||||
- Trajectory: scrollbar-gutter auto, overflow hidden/hidden
|
||||
- Trajectory: scrollbar-gutter auto, overflow hidden/auto
|
||||
- Trajectory scroller scrolls: false
|
||||
- Trajectory reserved band: 0px
|
||||
- input card left edge moves between tabs: 4px
|
||||
|
||||
@@ -272,12 +272,17 @@
|
||||
.scrollBody:has([data-conversation-composer-overlay]) {
|
||||
position: relative;
|
||||
/* A clipping box nothing scrolls out of, stated as a scroll container on both
|
||||
axes rather than `overflow: hidden`: WebKit honours the reservation above
|
||||
only in the `overflow-y: auto` form, and a single-axis scroller computes
|
||||
the other axis to `auto`
|
||||
([decision](../../../../../../.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md)). */
|
||||
axes rather than `overflow: hidden`: a single-axis scroller computes the
|
||||
other axis to `auto`, and the seat's absolute positioning below needs the
|
||||
box to stay a containing block in every engine. */
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
/* The view owns its own scrollers (the trajectory ledger scrolls inside the
|
||||
view), so this box never scrolls here; reserving a gutter would only
|
||||
narrow the view's content by the bar's width. Chat keeps the reservation
|
||||
on the same element, so the seat below compensates with the bar's width
|
||||
([decision](../../../../../../.agents/notes/implemented/bug-fix/2026-08-04-composer-tab-gutter-reservation.md)). */
|
||||
scrollbar-gutter: auto;
|
||||
}
|
||||
|
||||
.scrollBody:has([data-conversation-composer-overlay]) > .viewArea {
|
||||
@@ -288,7 +293,13 @@
|
||||
|
||||
.scrollBody:has([data-conversation-composer-overlay]) > .composerSeat {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
/* Chat's seat rides this box's content box, which its own scrolling shortens
|
||||
by the bar's width; the overlay seat is against the padding box, so it
|
||||
must give back the same width to keep the input card in place across
|
||||
tabs. Reads ui-theme's --dsh-scrollbar-width, which mirrors the WebKit
|
||||
bar's layout width; Firefox's thin bar resolves to the same 8px on the
|
||||
platforms this app targets, so the compensation holds there too. */
|
||||
right: var(--dsh-scrollbar-width);
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,11 @@
|
||||
body {
|
||||
--dsh-scrollbar-thumb: var(--dsw-alias-scrollbar-bg-l1);
|
||||
--dsh-scrollbar-thumb-hover: var(--dsw-alias-scrollbar-hover-l1);
|
||||
/* The WebKit bar's layout width, mirrored by the ::-webkit-scrollbar rule
|
||||
below. A surface that must align itself beside a space-consuming bar
|
||||
(ConversationRoot's overlay composer seat) reads this instead of
|
||||
hardcoding the number. */
|
||||
--dsh-scrollbar-width: 8px;
|
||||
}
|
||||
|
||||
/* The two paths are mutually exclusive, and the gate is load-bearing rather
|
||||
|
||||
Reference in New Issue
Block a user