From 369fad9faf61922b6b5f580fca66cf91e005bbfd Mon Sep 17 00:00:00 2001 From: _Kerman Date: Tue, 4 Aug 2026 14:56:30 +0800 Subject: [PATCH] fix(web): inset sidebar scrollbar by 2px --- apps/web/tests/sidebar-scrollbar.e2e.ts | 15 ++++++++++++--- .../sidebar-scrollbar/geometry.expected.md | 2 ++ .../src/client/WorkspaceBrowser.module.css | 13 ++++++++++--- .../ui-workspace/tests/browser-styles.spec.ts | 12 +++++++++--- 4 files changed, 33 insertions(+), 9 deletions(-) diff --git a/apps/web/tests/sidebar-scrollbar.e2e.ts b/apps/web/tests/sidebar-scrollbar.e2e.ts index c37a9d67c9..b690a9d916 100644 --- a/apps/web/tests/sidebar-scrollbar.e2e.ts +++ b/apps/web/tests/sidebar-scrollbar.e2e.ts @@ -106,7 +106,9 @@ interface ListMetrics { overflows: boolean /** Border-box width minus client width: the space the scrollbar takes out of the content area. */ band: number - /** Distance from the first row background's right edge to the list border box. */ + /** Distance from the scrollbar's right edge to the sidebar edge. */ + scrollbarEdgeOffset: number + /** Distance from the first row background's right edge to the sidebar edge. */ rowEdgeInset: number /** Client-area right edge in viewport coordinates (`clientWidth` excludes the scrollbar band). */ clientRight: number @@ -172,6 +174,8 @@ function measureList(page: Page): Promise { const pseudoWidth = getComputedStyle(list, '::-webkit-scrollbar').width const barWidth = pseudoWidth === 'auto' ? 15 : Number.parseFloat(pseudoWidth) const listRect = list.getBoundingClientRect() + const sidebarEdge = list.parentElement?.getBoundingClientRect().right + if (sidebarEdge === undefined) throw new Error('sidebar session list has no layout parent') return { gutter: style.scrollbarGutter, width: pseudoWidth, @@ -183,7 +187,8 @@ function measureList(page: Page): Promise { hoverToken: resolve('--dsh-scrollbar-thumb-hover'), overflows: list.scrollHeight > list.clientHeight, band: listRect.width - list.clientWidth, - rowEdgeInset: listRect.right - row.getBoundingClientRect().right, + scrollbarEdgeOffset: sidebarEdge - listRect.right, + rowEdgeInset: sidebarEdge - row.getBoundingClientRect().right, clientRight: listRect.left + list.clientWidth, borderRight: listRect.right, timeRight: time.getBoundingClientRect().right, @@ -211,9 +216,11 @@ function measureRowInset(page: Page): Promise('[role="treeitem"]') if (row === null) throw new Error('no row in the sidebar list') + const sidebarEdge = list.parentElement?.getBoundingClientRect().right + if (sidebarEdge === undefined) throw new Error('sidebar session list has no layout parent') return { overflows: list.scrollHeight > list.clientHeight, - rowEdgeInset: list.getBoundingClientRect().right - row.getBoundingClientRect().right, + rowEdgeInset: sidebarEdge - row.getBoundingClientRect().right, } }) } @@ -247,6 +254,7 @@ function renderGeometry(light: ListMetrics, dark: ListMetrics): string { `- --dsh-scrollbar-thumb-hover: ${metrics.hoverToken}`, `- list overflows: ${String(metrics.overflows)}`, `- reserved band: ${String(metrics.band)}px`, + `- scrollbar inset from the sidebar edge: ${String(metrics.scrollbarEdgeOffset)}px`, `- row background inset from the sidebar edge: ${String(metrics.rowEdgeInset)}px`, `- relative time covered by the bar: ${String(metrics.timeCoveredBy)}px`, `- relative time ends inside the content area: ${String(metrics.timeRight <= metrics.clientRight)}`, @@ -325,6 +333,7 @@ describe('web e2e: sidebar session list scrollbar (reserved gutter / themed thum // drawn over it. Removing the declaration makes it exactly 0. The value // itself is not pinned — it tracks `scrollbar-width` and the platform. expect(metrics.band).toBeGreaterThan(0) + expect(metrics.scrollbarEdgeOffset).toBe(2) expect(metrics.rowEdgeInset).toBe(12) // The reported symptom, stated directly: no part of the row's relative time // lies under the bar. Measures 7 on clean master — the `h` of `1h` is the diff --git a/apps/web/tests/snapshots/sidebar-scrollbar/geometry.expected.md b/apps/web/tests/snapshots/sidebar-scrollbar/geometry.expected.md index 48961d7fb5..b0fb597418 100644 --- a/apps/web/tests/snapshots/sidebar-scrollbar/geometry.expected.md +++ b/apps/web/tests/snapshots/sidebar-scrollbar/geometry.expected.md @@ -12,6 +12,7 @@ - --dsh-scrollbar-thumb-hover: rgb(212, 212, 212) - list overflows: true - reserved band: 8px +- scrollbar inset from the sidebar edge: 2px - row background inset from the sidebar edge: 12px - relative time covered by the bar: 0px - relative time ends inside the content area: true @@ -29,6 +30,7 @@ - --dsh-scrollbar-thumb-hover: rgb(84, 85, 87) - list overflows: true - reserved band: 8px +- scrollbar inset from the sidebar edge: 2px - row background inset from the sidebar edge: 12px - relative time covered by the bar: 0px - relative time ends inside the content area: true diff --git a/packages/client/ui-workspace/src/client/WorkspaceBrowser.module.css b/packages/client/ui-workspace/src/client/WorkspaceBrowser.module.css index e15f966044..0b8841a3a4 100644 --- a/packages/client/ui-workspace/src/client/WorkspaceBrowser.module.css +++ b/packages/client/ui-workspace/src/client/WorkspaceBrowser.module.css @@ -6,6 +6,7 @@ .root { --dsh-session-list-edge-inset: var(--dsh-sidebar-inline-padding); --dsh-session-list-scrollbar-width: 8px; + --dsh-session-list-scrollbar-offset: 2px; flex: 1; min-height: 0; display: flex; @@ -214,13 +215,19 @@ } /* List: the only scrolling region. Block children keep their design heights - under content overflow. The stable 8px themed scrollbar and the remaining - padding together equal the shell's right inset, with or without overflow. */ + under content overflow. The 2px edge offset, stable 8px themed scrollbar, + and remaining padding equal the shell's right inset, with or without + overflow, so moving the bar does not move the rows. */ .list { flex: 1; min-height: 0; overflow-y: auto; - padding-right: calc(var(--dsh-session-list-edge-inset) - var(--dsh-session-list-scrollbar-width)); + margin-right: var(--dsh-session-list-scrollbar-offset); + padding-right: calc( + var(--dsh-session-list-edge-inset) + - var(--dsh-session-list-scrollbar-width) + - var(--dsh-session-list-scrollbar-offset) + ); padding-bottom: 12px; scrollbar-gutter: stable; } diff --git a/packages/client/ui-workspace/tests/browser-styles.spec.ts b/packages/client/ui-workspace/tests/browser-styles.spec.ts index e9a52987ae..4165971bff 100644 --- a/packages/client/ui-workspace/tests/browser-styles.spec.ts +++ b/packages/client/ui-workspace/tests/browser-styles.spec.ts @@ -43,12 +43,18 @@ describe('WorkspaceBrowser.module.css list', () => { it('counts the themed scrollbar inside the shell trailing inset', () => { expect(root?.get('--dsh-session-list-edge-inset')).toBe('var(--dsh-sidebar-inline-padding)') expect(root?.get('--dsh-session-list-scrollbar-width')).toBe('8px') + expect(root?.get('--dsh-session-list-scrollbar-offset')).toBe('2px') expect(root?.get('padding-right')).toBe('var(--dsh-session-list-edge-inset)') expect(listArea?.get('margin-right')).toBe('calc(-1 * var(--dsh-session-list-edge-inset))') expect(declarations('.fade')?.get('right')).toBe('var(--dsh-session-list-edge-inset)') - expect(list?.get('padding-right')).toBe( - 'calc(var(--dsh-session-list-edge-inset) - var(--dsh-session-list-scrollbar-width))', - ) + expect(list?.get('margin-right')).toBe('var(--dsh-session-list-scrollbar-offset)') + expect(list?.get('padding-right')).toBe([ + 'calc(', + 'var(--dsh-session-list-edge-inset)', + '- var(--dsh-session-list-scrollbar-width)', + '- var(--dsh-session-list-scrollbar-offset)', + ')', + ].join(' ')) expect(declarations('.list::-webkit-scrollbar')).toBeUndefined() })