From 4222351f3ce24ac7a1cb48de169827cd2641e456 Mon Sep 17 00:00:00 2001 From: mesalogo Date: Sun, 13 Sep 2026 16:01:41 +0800 Subject: [PATCH] fix: keep every full-window overlay clickable over the titlebar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 预览、设置、跨项目活动和外部 知识库实例等弹窗与标题栏重叠时,控件无法悬停和点击的问题。 --- UI-DESIGN.md | 9 ++-- src/renderer/src/WorkspacePrimitives.test.tsx | 27 ++++++++++++ src/renderer/src/styles.css | 43 +++++++++++++------ 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/UI-DESIGN.md b/UI-DESIGN.md index 95b5f257..e0d62e90 100644 --- a/UI-DESIGN.md +++ b/UI-DESIGN.md @@ -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` 单行表面,与右侧工作栏标签栏等高,左右分隔线必须水平 对齐。该栏不重复放置新建入口。 diff --git a/src/renderer/src/WorkspacePrimitives.test.tsx b/src/renderer/src/WorkspacePrimitives.test.tsx index bff010da..ae44356b 100644 --- a/src/renderer/src/WorkspacePrimitives.test.tsx +++ b/src/renderer/src/WorkspacePrimitives.test.tsx @@ -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" diff --git a/src/renderer/src/styles.css b/src/renderer/src/styles.css index be06c487..701fa9b6 100644 --- a/src/renderer/src/styles.css +++ b/src/renderer/src/styles.css @@ -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; }