From 6e480ef4aeeb6769b718407f6dbc73b2bec97869 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Wed, 5 Aug 2026 14:33:42 +0800 Subject: [PATCH 1/2] fix(desktop): keep modal dialogs out of the window drag region MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The titlebar is the app's only `-webkit-app-region: drag` surface, and Chromium hit-tests drag regions from element rects without ever seeing the top layer. A `showModal()` dialog therefore paints over the titlebar while its controls inside that rect are still swallowed by the window manager. The lightbox close button sits in the top-right corner and was only half clickable for exactly this reason: the part of it overlapping a `no-drag` titlebar cluster took clicks, the rest reached the OS as a window drag. Subtract every modal dialog's own rect from the drag surface instead of patching one button — a modal owns what it covers and the titlebar beneath it is already inert. The attachment E2E now asserts the overlap is real and that the open lightbox computes `no-drag`. --- apps/desktop/e2e/attachment.spec.ts | 17 ++++++++++++++++- .../src/renderer/styles/shell-layout.css | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/apps/desktop/e2e/attachment.spec.ts b/apps/desktop/e2e/attachment.spec.ts index 5b98d32bde..5e5b34e374 100644 --- a/apps/desktop/e2e/attachment.spec.ts +++ b/apps/desktop/e2e/attachment.spec.ts @@ -109,7 +109,22 @@ test('a mixed attachment send has the Astryx message hierarchy', async ({ window expect(imageBox!.y + imageBox!.height).toBeLessThanOrEqual(bubbleBox!.y); await image.getByRole('button').click(); - await expect(page.locator('.astryx-lightbox')).toBeVisible(); + const lightbox = page.locator('.astryx-lightbox'); + await expect(lightbox).toBeVisible(); + + // The lightbox parks its close button in the window's top-right corner, which + // is inside the titlebar's drag rect. The OS hit-tests drag regions from + // element rects and never sees the top layer, so a modal that does not + // subtract its own rect leaks clicks on that button to the window manager as + // drags. Assert both halves of the contract: the overlap is real (otherwise + // the app-region assertion guards nothing), and the modal is `no-drag`. + const titlebar = page.locator('.maka-window-titlebar'); + const [titlebarBox, lightboxBox] = await Promise.all([titlebar.boundingBox(), lightbox.boundingBox()]); + expect(titlebarBox).not.toBeNull(); + expect(lightboxBox).not.toBeNull(); + expect(lightboxBox!.y).toBeLessThan(titlebarBox!.y + titlebarBox!.height); + await expect(lightbox).toHaveCSS('-webkit-app-region', 'no-drag'); + await page.keyboard.press('Escape'); await expect(page.locator('.astryx-lightbox')).not.toBeVisible(); }); diff --git a/apps/desktop/src/renderer/styles/shell-layout.css b/apps/desktop/src/renderer/styles/shell-layout.css index f8e78d9533..11133efa72 100644 --- a/apps/desktop/src/renderer/styles/shell-layout.css +++ b/apps/desktop/src/renderer/styles/shell-layout.css @@ -259,6 +259,21 @@ -webkit-app-region: drag; } +/* The other half of that drag surface: modal dialogs carve themselves out. + Chromium builds drag regions from element rects and the top layer is invisible + to that pass — a `showModal()` dialog paints over the titlebar, but any control + it places inside the titlebar rect still reaches the OS as a window drag. The + lightbox close button sits in the top-right corner and was half-dead for + exactly this reason: only the part of it overlapping a `no-drag` titlebar + cluster's rect responded to clicks. A modal owns everything it covers and the + titlebar under it is inert, so subtracting the whole dialog rect is the rule, + not a per-button patch — every `showModal()` surface (Astryx Dialog, Lightbox) + gets it. Declared after the drag rect and matching a later node in document + order, which is how Chromium resolves the subtraction. */ +dialog:modal { + -webkit-app-region: no-drag; +} + .maka-shell-topbar-rail { display: flex; align-items: center; From 083204bb91718ef59dae5e678f14f1e311f2d719 Mon Sep 17 00:00:00 2001 From: AstroHan Date: Wed, 5 Aug 2026 14:38:59 +0800 Subject: [PATCH 2/2] docs(desktop): trim the drag-region comments to the non-obvious fact --- apps/desktop/e2e/attachment.spec.ts | 9 +++------ apps/desktop/src/renderer/styles/shell-layout.css | 14 +++----------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/apps/desktop/e2e/attachment.spec.ts b/apps/desktop/e2e/attachment.spec.ts index 5e5b34e374..7572523575 100644 --- a/apps/desktop/e2e/attachment.spec.ts +++ b/apps/desktop/e2e/attachment.spec.ts @@ -112,12 +112,9 @@ test('a mixed attachment send has the Astryx message hierarchy', async ({ window const lightbox = page.locator('.astryx-lightbox'); await expect(lightbox).toBeVisible(); - // The lightbox parks its close button in the window's top-right corner, which - // is inside the titlebar's drag rect. The OS hit-tests drag regions from - // element rects and never sees the top layer, so a modal that does not - // subtract its own rect leaks clicks on that button to the window manager as - // drags. Assert both halves of the contract: the overlap is real (otherwise - // the app-region assertion guards nothing), and the modal is `no-drag`. + // The lightbox close button lands inside the titlebar's drag rect, where the + // OS eats clicks unless the modal is `no-drag`. The overlap is asserted first — + // without it the app-region check would guard nothing. const titlebar = page.locator('.maka-window-titlebar'); const [titlebarBox, lightboxBox] = await Promise.all([titlebar.boundingBox(), lightbox.boundingBox()]); expect(titlebarBox).not.toBeNull(); diff --git a/apps/desktop/src/renderer/styles/shell-layout.css b/apps/desktop/src/renderer/styles/shell-layout.css index 11133efa72..762be2ac33 100644 --- a/apps/desktop/src/renderer/styles/shell-layout.css +++ b/apps/desktop/src/renderer/styles/shell-layout.css @@ -259,17 +259,9 @@ -webkit-app-region: drag; } -/* The other half of that drag surface: modal dialogs carve themselves out. - Chromium builds drag regions from element rects and the top layer is invisible - to that pass — a `showModal()` dialog paints over the titlebar, but any control - it places inside the titlebar rect still reaches the OS as a window drag. The - lightbox close button sits in the top-right corner and was half-dead for - exactly this reason: only the part of it overlapping a `no-drag` titlebar - cluster's rect responded to clicks. A modal owns everything it covers and the - titlebar under it is inert, so subtracting the whole dialog rect is the rule, - not a per-button patch — every `showModal()` surface (Astryx Dialog, Lightbox) - gets it. Declared after the drag rect and matching a later node in document - order, which is how Chromium resolves the subtraction. */ +/* Drag regions are hit-tested from element rects, and the top layer is invisible + to that pass: a `showModal()` dialog paints over the titlebar, but its controls + inside the titlebar rect still reach the OS as window drags. */ dialog:modal { -webkit-app-region: no-drag; }