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; }