fix: keep every full-window overlay clickable over the titlebar

The drag-region fix applied to six dialogs left seven overlays uncovered,
including two added in the same batch, so their controls still stopped
responding to hover and clicks where they overlapped the native titlebar.
Three overlays used inset: 38px 0 0 to dodge the titlebar instead of
releasing the drag region, leaving an unmasked strip.

Replace the per-dialog declarations with one shared selector covering all
fifteen overlays and give each one inset: 0. Add a regression test that
discovers every fixed overlay from the stylesheet and fails when a new one
misses the shared rule, so this stops being fixed one dialog at a time.

Release note: 修复 MCP 编辑器、发布说明、远端目录选择、HTML 预览、设置、跨项目活动和外部
知识库实例等弹窗与标题栏重叠时,控件无法悬停和点击的问题。
This commit is contained in:
mesalogo
2026-09-13 16:01:41 +08:00
parent 8ad26d43bb
commit 4222351f3c
3 changed files with 63 additions and 16 deletions
+6 -3
View File
@@ -393,9 +393,12 @@
`34px × 34px` 点击区域。
- 发布说明 Modal 与其他应用级 Modal 一样使用整窗遮罩;遮罩必须覆盖主侧栏、右侧内容和
顶栏,不能保留一条未被遮罩的窗口控制色块。关闭操作由 Modal 自身提供。
- 整窗固定遮罩通过 `createPortal(..., document.body)` 挂载,并声明
`-webkit-app-region: no-drag`,避免页面层叠上下文中的遮罩受 Electron 原生拖动区域影响。
保留 React Context、关闭与焦点管理;表单样式不得依赖已移出的页面祖先选择器。
- 整窗固定遮罩通过 `createPortal(..., document.body)` 挂载,避免页面层叠上下文影响;
保留 React Context、关闭与焦点管理,表单样式不得依赖已移出的页面祖先选择器。
- 遮罩必须 `inset: 0` 铺满整窗,并由 `styles.css` 中统一的遮罩选择器声明
`-webkit-app-region: no-drag`,否则 `.brand` 与 `.topbar` 的原生拖动区域会截获指针,
使遮罩上的控件无法悬停和点击。新增整窗遮罩时扩展该共享选择器,不要在各自规则里复制
`no-drag`,也不要用 `inset: 38px 0 0` 之类的偏移绕开标题栏。
- 当前会话存在 Task 时,Task 上下文栏位于标题栏下方并承载任务摘要与详情;没有 Task 时
整行隐藏。折叠状态保持为 `46px` 单行表面,与右侧工作栏标签栏等高,左右分隔线必须水平
对齐。该栏不重复放置新建入口。
@@ -127,6 +127,33 @@ describe('WorkspacePrimitives', () => {
)
})
it('releases the native drag region for every full-window overlay', () => {
const noDragBlock = stylesheet.match(
/:is\(([^)]*(?:-backdrop|-modal)[^)]*)\)\s*\{\s*-webkit-app-region:\s*no-drag;\s*\}/u
)
expect(noDragBlock).not.toBeNull()
const covered = new Set(
noDragBlock![1]!.split(',').map((selector) => selector.trim())
)
// Any fixed overlay painting over .brand or .topbar must release the drag
// region, otherwise the native titlebar keeps the pointer and its controls
// stop responding to hover and clicks.
const overlays = [
...stylesheet.matchAll(
/^(\.[\w-]*(?:backdrop|modal))\s*\{([^}]*)\}/gmu
)
].filter(([, , body]) => /position:\s*fixed/u.test(body!))
expect(overlays.length).toBeGreaterThan(10)
for (const [, selector, body] of overlays) {
expect(covered, `${selector} must release the drag region`).toContain(
selector
)
expect(body, `${selector} must cover the whole window`).toMatch(
/inset:\s*0;/u
)
}
})
it('uses bundled variable fonts and readable shared type tokens', () => {
expect(rendererEntry).toContain(
"@fontsource-variable/noto-sans-sc/wght.css"
+30 -13
View File
@@ -2878,7 +2878,6 @@ textarea:focus-visible {
background: var(--overlay-backdrop);
inset: 0;
place-items: center;
-webkit-app-region: no-drag;
}
.project-create-card {
@@ -2895,7 +2894,6 @@ textarea:focus-visible {
box-shadow: var(--shadow-dialog);
flex-direction: column;
gap: 10px;
-webkit-app-region: no-drag;
}
.project-create-card > header {
@@ -2906,7 +2904,6 @@ textarea:focus-visible {
align-items: center;
justify-content: space-between;
flex: 0 0 auto;
-webkit-app-region: no-drag;
}
.project-create-card .project-create-card__close {
@@ -2915,7 +2912,6 @@ textarea:focus-visible {
width: 40px;
height: 40px;
flex: 0 0 auto;
-webkit-app-region: no-drag;
}
.project-create-card__body {
@@ -3746,6 +3742,33 @@ textarea:focus-visible {
-webkit-app-region: no-drag;
}
/*
* Overlays are portalled to document.body and cover the whole window, so they
* paint over the .brand and .topbar drag regions. Without no-drag the native
* titlebar keeps the pointer and their controls stop responding to hover and
* clicks. Every full-window overlay must match this selector; extend the list
* rather than adding a per-dialog copy.
*/
:is(
.project-create-backdrop,
.remote-directory-picker-backdrop,
.html-preview-viewer-backdrop,
.image-viewer-backdrop,
.release-notes-backdrop,
.settings-backdrop,
.channel-qr-backdrop,
.feedback-dialog-backdrop,
.document-parsing-diagnostic-backdrop,
.mcp-editor-backdrop,
.knowledge-confirm-dialog-backdrop,
.knowledge-library-dialog-backdrop,
.knowledge-dialog-backdrop,
.project-activity__backdrop,
.external-knowledge-modal
) {
-webkit-app-region: no-drag;
}
.window-controls {
display: flex;
height: 100%;
@@ -5916,7 +5939,7 @@ button > svg {
display: grid;
padding: var(--space-4);
background: var(--overlay-backdrop);
inset: 38px 0 0;
inset: 0;
place-items: center;
}
@@ -6955,7 +6978,6 @@ button > svg {
}
.image-viewer-backdrop {
-webkit-app-region: no-drag;
position: fixed;
z-index: 80;
display: grid;
@@ -9936,7 +9958,6 @@ details.settings-section > :not(summary) + :not(summary) {
}
.channel-qr-backdrop {
-webkit-app-region: no-drag;
position: fixed;
z-index: var(--z-dialog);
display: grid;
@@ -10062,7 +10083,6 @@ details.settings-section > :not(summary) + :not(summary) {
}
.feedback-dialog-backdrop {
-webkit-app-region: no-drag;
position: fixed;
z-index: var(--z-dialog);
display: grid;
@@ -10752,7 +10772,6 @@ details.settings-section > :not(summary) + :not(summary) {
}
.document-parsing-diagnostic-backdrop {
-webkit-app-region: no-drag;
position: fixed;
z-index: var(--z-dialog);
display: grid;
@@ -11881,7 +11900,7 @@ details.settings-section > :not(summary) + :not(summary) {
display: grid;
padding: var(--space-4);
background: var(--overlay-backdrop);
inset: 38px 0 0;
inset: 0;
place-items: center;
}
@@ -12798,7 +12817,6 @@ details.settings-section > :not(summary) + :not(summary) {
}
.knowledge-confirm-dialog-backdrop {
-webkit-app-region: no-drag;
position: fixed;
z-index: var(--z-dialog);
display: grid;
@@ -12841,7 +12859,6 @@ details.settings-section > :not(summary) + :not(summary) {
}
.knowledge-library-dialog-backdrop {
-webkit-app-region: no-drag;
position: fixed;
z-index: var(--z-dialog);
display: grid;
@@ -16662,7 +16679,7 @@ details.settings-section > :not(summary) + :not(summary) {
display: grid;
padding: var(--space-4);
background: var(--overlay-backdrop);
inset: 38px 0 0;
inset: 0;
place-items: center;
}