🐛 fix(client): keep frame content inside vertical safe areas

This commit is contained in:
Saafo committed 2026-10-20 15:07:37 +08:00
1 parent 788c22d007
commit 9b029cdb38
10 files changed
+53 -6

No files matched your search

+39
View File
@@ -0,0 +1,39 @@
/** Layout consumes the Web shell's vertical safe insets without resizing the viewport. */
import { fileURLToPath } from 'node:url'
import { chromium } from 'playwright'
import { expect, it } from 'vitest'
import { launchWebScaffold } from './scaffold.ts'
it('keeps sidebar and conversation content inside top and bottom safe areas', async () => {
const scaffold = await launchWebScaffold({
profile: { packages: [] },
extraOverlayPath: fileURLToPath(new URL('./pin-browse-picker.overlay.yml', import.meta.url)),
})
try {
const browser = await chromium.launch()
try {
const page = await browser.newPage({ viewport: { width: 390, height: 844 } })
await page.goto(scaffold.authenticatedUrl)
const center = page.locator('[class*="centerCol"]')
await center.waitFor()
// Desktop Chromium has no hardware cutout; supply the shell variables at their real owner.
await page.locator('#root').evaluate((root) => {
root.style.setProperty('--dsh-safe-area-top', '47px')
root.style.setProperty('--dsh-safe-area-bottom', '34px')
})
await expect.poll(async () => (await center.boundingBox())?.y).toBe(47)
expect((await center.boundingBox())?.height).toBe(763)
expect((await page.locator('[class*="sidebarCol"]').boundingBox())?.y).toBe(47)
const input = page.getByRole('textbox').first()
await input.focus()
const box = await input.boundingBox()
expect(box!.y + box!.height).toBeLessThanOrEqual(810)
await page.locator('#root').evaluate((root) => {
root.style.removeProperty('--dsh-safe-area-top')
root.style.removeProperty('--dsh-safe-area-bottom')
})
await expect.poll(async () => (await center.boundingBox())?.y).toBe(0)
expect((await center.boundingBox())?.height).toBe(844)
} finally { await browser.close() }
} finally { await scaffold.close() }
})
+1
View File
@@ -103,6 +103,7 @@
"tests/settings-chrome.e2e.ts",
"tests/session-log-upload.e2e.ts",
"tests/caption-overlays.e2e.ts",
"tests/safe-area.e2e.ts",
"tests/settings-appearance.e2e.ts",
"tests/menu-material.e2e.ts",
"tests/desktop-locale.e2e.ts",
+2 -2
View File
@@ -15,8 +15,8 @@
en: d152484eb41ac6b4
zh: 09388d293f9be9cb
/deepseek-ai-dsh-client-ui-layout/use-this-package:
en: cc6e9021ba3001bc
zh: 97444857762b4e8e
en: 6e10526aaa310120
zh: 8cd703f499c63dce
/deepseek-ai-dsh-client-ui-layout/use-this-package/window-chrome-seat:
en: 7eac53b38d0777fe
zh: 7f9422dff671ba50
+2
View File
@@ -29,6 +29,8 @@ Desktop Mod+B toggles the left sidebar through the same layout action used by it
The root slot composes the sidebar, main content, and right column. The sidebar spans 264–420px, defaults to 280px, and retains a 56px rail when collapsed; below 1024px it collapses automatically, and opening the right panel collapses a manually expanded sidebar. The right panel first opens at 45% of the viewport, then retains the user's pixel preference, capped at 70%. To protect 400px for the center, the frame first reduces the right panel to 300px, then reports insufficient room so its occupant closes it, and only then compresses the center further. Dragging has no transition delay; the right handle is absent while closed or fullscreen.
The frame applies the Web shell’s top and bottom safe-area variables as vertical padding, defaulting to zero when absent. Its outer height stays equal to the layout viewport.
Global panels occupy the root-scoped `main` keyed slot; `conversation` is the reserved key for the Conversation. `ctx.layout.selectPanel(id)` selects a registered panel, and `null` selects the Conversation without changing the current Session. The Plugins page contributes one of the shipped global panels.
The single root-scoped `shell.bottom` slot spans the full width below the three columns. Its occupant controls its own height and visibility; the columns shrink by that height, and empty content reserves no space. Main-panel navigation preserves the bottom component. Column resize handles stop above it, while `shell.overlay` covers the whole frame. The default composition leaves this slot empty.
+2
View File
@@ -29,6 +29,8 @@ kind: "package-reference"
本插件在 root slot 中组合侧边栏、主内容和右栏。侧边栏宽度为 264~420px,默认为 280px,收起后保留 56px 控制栏;窗口宽度低于 1024px 时自动收起,打开右侧面板也会收起手动展开的侧边栏。右侧面板首次打开时使用视口宽度的 45%,之后保留用户的像素宽度偏好,上限为 70%。为给中栏保留 400px,框架先将右侧面板缩减至 300px,再报告空间不足,使占用方将其关闭,最后才进一步压缩中栏。拖动没有过渡延迟;右侧手柄在关闭或全屏时不显示。
框架将 Web 壳提供的上下安全区变量用于垂直内边距,变量缺省时按零处理。框架外部高度始终等于布局视口。
全局面板占据 root 作用域的 `main` keyed slot;`conversation` 是为会话界面保留的 key。`ctx.layout.selectPanel(id)` 选中已注册面板,`null` 则选中会话界面,但不改变当前会话。Plugins 页面是默认组合中的一个全局面板。
root 作用域的单一 `shell.bottom` slot 横跨三列下方的整行。占用方控制自身高度与显隐;三列按该高度缩短,空内容不预留空间。切换主面板时保留底部组件。列宽拖拽手柄止于底部内容上方,`shell.overlay` 仍覆盖整个框架。默认组合不占用此 slot。
@@ -3,6 +3,8 @@
display: grid;
grid-template-rows: minmax(0, 1fr) auto;
height: 100%;
box-sizing: border-box;
padding-block: var(--dsh-safe-area-top, 0px) var(--dsh-safe-area-bottom, 0px);
overflow: hidden;
background: var(--dsw-alias-bg-base);
}
+2 -2
View File
@@ -18,8 +18,8 @@
en: 5dba7f9e0c6a94c8
zh: f160f806c487337b
/deepseek-ai-dsh-client-web/use-this-package/viewport-sizing:
en: c29bc7d1604312ce
zh: 833c2a24d9a445e7
en: e7b19d8f2c771b70
zh: fe517c603cb5a162
/deepseek-ai-dsh-client-web/use-this-package/what-boot-looks-like:
en: f3b01aa878764e5c
zh: f02eab65d441bfe0
+1 -1
View File
@@ -40,7 +40,7 @@ Web and Desktop mathematical rendering requires WOFF2 support. The [Web build](.
The root fills the CSS layout viewport with absolute positioning. The browser pans the page to reveal focused inputs; inner panes own content scrolling.
The root applies horizontal safe-area padding and publishes `--dsh-safe-area-top` and `--dsh-safe-area-bottom` for frame content padding.
The root applies horizontal safe-area padding and publishes `--dsh-safe-area-top` and `--dsh-safe-area-bottom`. The layout frame consumes these as vertical padding, keeping its sidebar and conversation, including the composer, inside the safe area.
The [Web application entry](../../../apps/web/index.html) declares `width=device-width, initial-scale=1, viewport-fit=cover` without limiting user zoom. Touch inputs use a minimum font size of 16px to avoid focus-triggered page enlargement.
+1 -1
View File
@@ -40,7 +40,7 @@ Web 和 Desktop 的数学公式渲染要求支持 WOFF2。[Web 构建](../../../
根容器通过绝对定位铺满 CSS 布局视口。浏览器平移页面以露出获得焦点的输入框,内容滚动由内部面板负责。
根容器应用左右安全区边距,并发布 `--dsh-safe-area-top` 和 `--dsh-safe-area-bottom`,供框架为内容保留上下边距。
根容器应用左右安全区边距,并发布 `--dsh-safe-area-top` 和 `--dsh-safe-area-bottom`。布局框架将这两个值用于上下内边距,使侧栏和包含输入框的会话区域位于安全区内。
[Web 应用入口](../../../apps/web/index.html)声明 `width=device-width, initial-scale=1, viewport-fit=cover`,不限制用户缩放。触摸输入框使用至少 16px 的字号,避免获得焦点时自动放大页面。
+1
View File
@@ -96,6 +96,7 @@
"apps/web/tests/settings-chrome.e2e.ts",
"apps/web/tests/session-log-upload.e2e.ts",
"apps/web/tests/caption-overlays.e2e.ts",
"apps/web/tests/safe-area.e2e.ts",
"apps/web/tests/settings-appearance.e2e.ts",
"apps/web/tests/menu-material.e2e.ts",
"apps/web/tests/desktop-locale.e2e.ts",