Conversation
打开设置浮层时,AppChrome 会在 flex 列里额外插入一个 32px 的 WindowsTitleBar。它是普通兄弟节点而非绝对定位,下面承载 ChatPage 的 flex-1 容器因此当场少掉 32px;关闭时再还回去。ChatPage 全程未卸载, 只是被压扁重排,于是每次切换都能看到整页上下位移。 跳变还和 300ms 的淡入淡出错位:visible 首帧即为 true(标题栏立刻 插入),active 要等两个 rAF 后才置位(浮层才开始淡入);关闭时标题栏 要等 transitionend 才移除,位移正好落在动画播完、视野最清晰的一刻。 改为让浮层自带标题栏,外层不再随开关增删: - 删除 AppChrome 的 standaloneTitleBar 条件渲染与对应 prop - 浮层加 flex flex-col 并在内部渲染 WindowsTitleBar,另套一层 min-h-0 flex-1 收住 SettingsShell 的 h-full(AppErrorBoundary 无错时直接透传 children,否则设置内容会顶穿标题栏) 顺带修掉两处同源风险: - macOS:--app-header-height 原先由 ChatPage 子树里的 MacOsTitleBarToggle 写入、卸载时 removeProperty,子树一旦重挂 header 高度就会掉回 48px 兜底。抽成 useMacOsAppHeaderHeight() 挂在 App 根,卸载写回默认值;tokens.css 的 :root 补静态默认值 - Web:gateway shell 与设置浮层各自堆叠了 h-100vh/h-100svh/ [height:100dvh],谁生效取决于类名顺序,而 svh 与 dvh 在移动端 地址栏收起时不等值。统一收敛到 --app-viewport-height, 由 @supports 决定取 dvh 还是 vh 验证:两端 tsc 通过;test/settings 三个浮层测试全绿;Web 端 vite build 产物中 100svh 仅剩 token 定义、无选择器引用。 test/chat/standard-overlays.test.mjs 在干净基线上同样失败(Windows 下 virtual-core 路径解析问题),与本次改动无关。
在 SettingsShell 里接入 useSettingsEscapeToClose,桌面端与 Web 端共用 同一条路径。返回按钮补 aria-keyshortcuts="Escape",桌面端侧栏按钮右侧 增加 Esc 角标与 title 提示。 监听挂在 window 而不是 document,这一点是有意为之。设置页内部已有多处 ESC 消费方——Base UI 的 Dialog、下拉弹层、内联重命名输入框——它们必须 先拿到按键。这些层都绑在 document 上,而同一目标上的监听器按注册顺序 触发,设置页先于弹窗挂载,因此绑 document 会让本 hook 抢先执行,一次 按键既关弹窗又退回对话。window 在冒泡路径上恒位于 document 之后,与 订阅时机无关,把顺序从挂载巧合变成结构保证。 这个缺陷是先写测试才暴露的:初版绑 document,"被消费的 ESC 不应再关闭 设置页"一例直接失败(closes 为 2)。 其余守卫: - defaultPrevented:Base UI useDismiss 关闭未被取消时会 preventDefault, 据此让位给嵌套层 - repeat:长按 ESC 不应按重复节拍反复触发 - isComposing:输入法组字期间的 ESC 用于取消组字,不退出页面 未排除文本输入框:设置项里按 ESC 语义即"我改完了",而确实需要独占该键的 字段(供应商模型搜索、内联重命名)自身已 stopPropagation。 测试覆盖 hook 的各分支、卸载后不再残留监听,以及 SettingsShell 的集成 链路与无障碍属性。test/settings 下 21 个用例全部通过。
StackCairn
marked this pull request as draft
September 20, 2026 05:54
Contributor
|
PR governance checks passed. Awaiting human review. |
AlphaCatMeow
marked this pull request as ready for review
September 20, 2026 05:57
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #835
Summary
对话页和系统设置之间切换时,整页内容会上下位移一次。
根因是设置浮层的开合被直接接到了标题栏的渲染开关上:
<AppChrome standaloneTitleBar={settingsOpen}>。AppChrome是纵向 flex 列,浮层一开,列里就多出一个h-8(32px)的WindowsTitleBar。它是普通兄弟节点而非绝对定位,下面承载ChatPage的flex-1容器因此当场少掉 32px;关掉再还回去。ChatPage全程没有卸载,只是被压扁重排。位移之所以刺眼,是因为它和 300ms 的淡入淡出错开了。
visible(overlay !== "closed")首帧即为 true,标题栏立刻插入,而active(overlay === "open")要等两个 rAF 后才置位、浮层才开始淡入,中间几帧用户看到的是一个没有任何遮挡的对话页在跳。关闭时标题栏要等transitionend才移除,那下 32px 的下坠正好落在动画播完、画面已经静止的时刻。改法是让浮层自带标题栏,外层不再随开关增删。对话页所在的
flex-1容器高度从此恒定,跳变从源头消失,而ChatPage的顶栏结构一行没动。顺带修掉两处同源风险。macOS 的
--app-header-height原先由ChatPage子树里的MacOsTitleBarToggle写入、卸载时removeProperty,子树一旦重挂,header 高度就会掉回 48px 兜底;现在抽成useMacOsAppHeaderHeight()挂在 App 根,卸载写回默认值,tokens.css的:root也补了静态默认值。Web 端 gateway shell 与设置浮层各自堆叠了h-100vh/h-100svh/[height:100dvh],谁生效取决于类名顺序,而svh与dvh在移动端地址栏收起时并不等值,现在统一收敛到--app-viewport-height,由@supports决定取dvh还是vh。第二个 commit 给设置页加了 ESC 返回对话。监听挂在
window而不是document,这点是有意为之:设置页内部已有多处 ESC 消费方(Base UI 的 Dialog、下拉弹层、内联重命名输入框),它们都绑在document上,而同一目标上的监听器按注册顺序触发,设置页先于弹窗挂载。绑document会让本 hook 抢先执行,一次按键既关弹窗又退回对话。window在冒泡路径上恒位于document之后,与订阅时机无关,把顺序从挂载巧合变成结构保证。这个缺陷是先写测试才暴露的,初版绑document时「被消费的 ESC 不应再关闭设置页」一例直接失败。Change scope
crates/agent-gui/src/App.tsx— 删除standaloneTitleBar条件渲染与对应 prop,浮层内部渲染标题栏crates/agent-gui/src/components/MacOsTitleBarSpacer.tsx— 新增useMacOsAppHeaderHeight()crates/agent-ui/src/styles/tokens.css—--app-header-height静态默认值、--app-viewport-heightcrates/agent-gateway/web/src/lib/webStyleClasses.ts— 视口高度收敛到单一变量crates/agent-ui/src/lib/settings/useSettingsEscapeToClose.ts— 新增crates/agent-ui/src/pages/settings/SettingsShell.tsx— 接入 ESC,返回按钮补aria-keyshortcuts与 Esc 角标crates/agent-ui/src/i18n/translations/{zhCN,enUS}Settings.ts— 新增settings.backToChatHintcrates/agent-gui/test/settings/settings-escape-to-close.test.mjs— 新增Screenshots / preview
Verification
类型检查,两端均通过:
测试:
test/settings下 21 个用例全部通过,其中settings-overlay.test.mjs覆盖浮层状态机,新增的settings-escape-to-close.test.mjs覆盖 ESC 各分支(defaultPrevented让位、repeat、isComposing、卸载后不残留监听)与SettingsShell的集成链路和无障碍属性。Web 端
vite build通过,CSS 产物中--app-viewport-height的两条定义和@supports块正确生成,100svh只剩 token 定义本身、没有选择器再引用。手工验证:Windows 桌面端与 WebUI 窄屏各来回切换十余次,跳变消失。
test/chat/standard-overlays.test.mjs与test/settings/style-token-contract.test.mjs在本机失败,但 stash 掉全部改动、在干净基线上跑同样失败,原因是load-ts-module.mjs在 Windows 下把路径拼成了D:\D:\...,与本 PR 无关。macOS 的那处修复我只能做代码层面的推理,手上没有 mac 实机,劳烦 review 时留意。
Pre-submit checklist
最后一项没有勾:ESC 返回是用户可见的新行为,但仓库里没有面向用户的快捷键清单文档可以更新。按键本身在返回按钮上有 Esc 角标和
aria-keyshortcuts提示。排查与修复过程记在docs/worklog/settings-chat-switch-height-jump.md。如果需要补一份快捷键文档,告诉我放哪个目录,我加上。