Merge pull request #4491 from deepseek-harness/fix/windows-menu-tooltip-overlap

fix(desktop): place Windows caption tooltips below the titlebar
This commit is contained in:
Xu Hanxiang authored and GitHub committed 2026-09-20 16:02:54 +08:00
commit 5dc6767d7b
8 files changed
+49 -8

No files matched your search

@@ -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 packages/client/ui-settings-general/README.md
README.md: cf8bb5a75aa90842108646b57ceec67ada8ae711
README.zh.md: 7df3eee13df7bd041cc5f09288d1b7fe28acded6
README.md: ebdb22adc071b5df435fddd6620dd982d866aa36
README.zh.md: 0bb188ea5024b14e68baa1a687a654ee8963f3d7
@@ -103,6 +103,7 @@ None; this package neither assembles nor sends a provider request.
These limits define what the shell itself provides versus what features must supply; they are current package constraints.
- **Additional General rows require their feature plugins** — the shell supplies Developer tools; feature plugins supply the remaining preferences.
- **The Windows caption badge keeps a side-opening bubble** — `DesktopUpdateBadge` occupies `sidebar.toggle.badge` in the caption and requests `side="right"`, so the Desktop-owned menu text can cover its bubble while the sidebar is collapsed on Windows; the sidebar toggle and New Session bubbles open below the caption instead (#4688).
<a id="dev-note"></a>
### Dev Note
@@ -103,6 +103,7 @@ kind: "package-reference"
这些限制说明外壳自身提供什么、功能包必须提供什么;它们是当前包约束。
- **额外的通用设置行需要相应功能插件**:外壳提供开发者工具;功能插件提供其他偏好。
- **Windows 顶栏徽标的气泡仍向右展开**:`DesktopUpdateBadge` 占用顶栏的 `sidebar.toggle.badge` 且请求 `side="right"`,侧栏收起时其气泡会被 Desktop 菜单文字遮挡;侧栏开关与新建会话的气泡则改在顶栏下方展开(#4688)。
<a id="dev-note"></a>
### 开发备注
+2 -2
View File
@@ -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 packages/client/ui-sidebar/README.md
README.md: 1c5306b8b3efa5d7908cfd33206acefef20fc3a8
README.zh.md: d72da040252f391bcc1468c9e29adf6a5ef85ef0
README.md: 94bb3a70ca8c06b5838f41436bb0f2ba2751501e
README.zh.md: 86f54882ba1c96b9b50898993931757f7afef4e5
+1 -1
View File
@@ -41,7 +41,7 @@ The top expand button hosts the optional, non-interactive `sidebar.toggle.badge`
During a live collapse, the expanded content fades out at its current width, the upper controls share one fade and leftward translation into the 56px rail, and the layout's column slide ends the motion. A page that starts collapsed renders the rail statically, and reduced-motion mode disables both transitions. The bottom-pinned `sidebar.settings` control shares the fade timing but has no horizontal translation.
On Windows Electron, `html[data-windows-titlebar]` fixes the sidebar toggle in the caption's top-left corner in both states, aligned with New Session's left edge only when expanded. The expanded brand sits below the caption and above New Session, with 8px of extra space above that button. Collapsing hides the brand and sidebar content and places New Session between the sidebar toggle and the Desktop-owned menus. The sidebar sets the root `--dsh-windows-menu-start` to 84px when collapsed; the Desktop preload uses it to position its menu after New Session and defaults to 48px when expanded. Caption icon buttons use centered 16px glyphs in 28px circular controls and exclude themselves from the window drag region.
On Windows Electron, `html[data-windows-titlebar]` fixes the sidebar toggle in the caption's top-left corner in both states, aligned with New Session's left edge only when expanded. The expanded brand sits below the caption and above New Session, with 8px of extra space above that button. Collapsing hides the brand and sidebar content and places New Session between the sidebar toggle and the Desktop-owned menus. The sidebar sets the root `--dsh-windows-menu-start` to 84px when collapsed; the Desktop preload uses it to position its menu after New Session and defaults to 48px when expanded. Caption icon buttons use centered 16px glyphs in 28px circular controls and exclude themselves from the window drag region. The sidebar toggle and New Session bubbles open below the caption, where the Desktop-owned menu text cannot cover them; an occupying `sidebar.toggle.badge` chooses its own bubble side.
### macOS desktop
+1 -1
View File
@@ -41,7 +41,7 @@ dsh Web 客户端的侧边栏让用户识别当前构建、启动新会话、将
实时收起时,展开内容在当前宽度淡出,上方控件共用同一段透明度渐变,并向左平移进入 56px 轨道,由布局的栏滑动结束整段动画。页面初始即为收起状态时会静态渲染轨道;减少动态效果模式会禁用两段过渡。固定在底部的 `sidebar.settings` 控件共用相同的透明度渐变时序,但不发生横向位移。
在 Windows Electron 中,`html[data-windows-titlebar]` 将两种状态下的侧栏开关固定在顶栏左上角,仅在展开态与新建会话按钮左边缘对齐。展开态品牌位于顶栏下方、新建会话按钮上方,按钮上方额外留出 8px。收起后,品牌和侧栏内容隐藏,新建会话按钮排在侧栏开关与 Desktop 菜单之间。侧栏在收起态将根元素的 `--dsh-windows-menu-start` 设为 84px;Desktop preload 使用它将菜单放在新建会话之后,展开态默认为 48px。顶栏图标按钮采用 28px 圆形控件中的居中 16px 图标,并从窗口拖拽区域中排除。
在 Windows Electron 中,`html[data-windows-titlebar]` 将两种状态下的侧栏开关固定在顶栏左上角,仅在展开态与新建会话按钮左边缘对齐。展开态品牌位于顶栏下方、新建会话按钮上方,按钮上方额外留出 8px。收起后,品牌和侧栏内容隐藏,新建会话按钮排在侧栏开关与 Desktop 菜单之间。侧栏在收起态将根元素的 `--dsh-windows-menu-start` 设为 84px;Desktop preload 使用它将菜单放在新建会话之后,展开态默认为 48px。顶栏图标按钮采用 28px 圆形控件中的居中 16px 图标,并从窗口拖拽区域中排除。侧栏开关与新建会话的悬停提示在顶栏下方展开,Desktop 菜单文字不会盖住它们;占用 `sidebar.toggle.badge` 的控件自行决定气泡展开方向。
### macOS 桌面
@@ -107,6 +107,10 @@ export function SidebarRoot({
}, [collapsed])
const windowsTitlebar = document.documentElement.hasAttribute('data-windows-titlebar')
const wide = windowsTitlebar ? !collapsed : !collapsed || !settled
// The Windows caption menus occupy the strip to the right of these controls
// (that is what --dsh-windows-menu-start reserves), so a right-side bubble
// lands under their text. Below the caption is the only clear side.
const captionTooltipSide = windowsTitlebar ? 'bottom' : 'right'
// Freeze the content at its expanded width while it fades out (collapsed
// && wide): the sliding column then clips it instead of reflowing it. The
@@ -169,7 +173,7 @@ export function SidebarRoot({
// (the expand affordance, figma sidebar-hover flow). Expanded it is a plain
// panel icon.
const toggle = (
<Tooltip label={collapsed ? t('toggle.open') : t('toggle.collapse')} delayMs={500}>
<Tooltip label={collapsed ? t('toggle.open') : t('toggle.collapse')} delayMs={500} side={captionTooltipSide}>
<button
type="button"
className={clsx(css.iconButton, css.toggle)}
@@ -247,7 +251,7 @@ export function SidebarRoot({
</div>
{/* Expanded, the button carries its own label — tooltip only on the rail. */}
<Tooltip label={t('session.new.label')} delayMs={500} disabled={wide}>
<Tooltip label={t('session.new.label')} delayMs={500} disabled={wide} side={captionTooltipSide}>
<button
type="button"
className={css.newSession}
@@ -245,3 +245,38 @@ it('wires the shell.leading controls to the shared sidebar actions', () => {
expect(toggleSidebar).toHaveBeenCalledOnce()
expect(startSession).toHaveBeenCalledOnce()
})
describe('Windows caption tooltips', () => {
afterEach(() => { document.documentElement.removeAttribute('data-windows-titlebar') })
const hover = (button: HTMLElement): void => {
fireEvent.mouseEnter(button)
act(() => { vi.advanceTimersByTime(500) })
}
it.each([false, true])(
'drops the sidebar toggle bubble below the caption (collapsed=%s)',
(collapsed) => {
vi.useFakeTimers()
document.documentElement.setAttribute('data-windows-titlebar', '')
mountShell({ collapsed, width: collapsed ? 0 : 300 })
hover(screen.getByRole('button', { name: collapsed ? 'Open sidebar' : 'Collapse sidebar' }))
expect(screen.getByRole('tooltip').getAttribute('data-side')).toBe('bottom')
},
)
it('drops the collapsed New Session bubble below the caption as well', () => {
vi.useFakeTimers()
document.documentElement.setAttribute('data-windows-titlebar', '')
mountShell({ collapsed: true, width: 0 })
hover(screen.getByRole('button', { name: 'New session' }))
expect(screen.getByRole('tooltip').getAttribute('data-side')).toBe('bottom')
})
it('keeps the ordinary Web bubble beside its anchor', () => {
vi.useFakeTimers()
mountShell({ collapsed: true, width: 0 })
hover(screen.getByRole('button', { name: 'Open sidebar' }))
expect(screen.getByRole('tooltip').getAttribute('data-side')).toBe('right')
})
})