diff --git a/desktop/playwright.config.ts b/desktop/playwright.config.ts index c658fbb769..0c595e4447 100644 --- a/desktop/playwright.config.ts +++ b/desktop/playwright.config.ts @@ -72,6 +72,7 @@ export default defineConfig({ "**/threadpane-ultrawide.spec.ts", "**/animated-avatar.spec.ts", "**/reminders.spec.ts", + "**/reminder-click-repro.spec.ts", "**/virtualization.spec.ts", "**/scroll-history.spec.ts", "**/channel-dense-second-reach.spec.ts", diff --git a/desktop/src/features/sidebar/ui/CustomChannelSection.tsx b/desktop/src/features/sidebar/ui/CustomChannelSection.tsx index 5818c430ad..43115c0b68 100644 --- a/desktop/src/features/sidebar/ui/CustomChannelSection.tsx +++ b/desktop/src/features/sidebar/ui/CustomChannelSection.tsx @@ -411,11 +411,7 @@ export function ChannelGroupSection({ items.length > 0 ? ( {items.map((channel) => ( - // modal={false}: menu items (e.g. Leave channel) open a modal - // AlertDialog. A modal ContextMenu would leave `pointer-events: none` - // stuck on when it closes as the dialog mounts, freezing the - // whole app. Non-modal avoids installing that body guard entirely. - + {draggable ? ( @@ -610,10 +606,7 @@ export function CustomChannelSection({ )} data-section-actions-open={actionsMenuOpen || undefined} > - {/* modal={false}: Rename/Delete section open a modal dialog; - a modal ContextMenu would leave `pointer-events: none` stuck on - after it closes, freezing the app. */} - +
@@ -702,10 +695,7 @@ export function CustomChannelSection({ {channels.length > 0 ? ( {channels.map((channel) => ( - // modal={false}: see note on the other channel ContextMenu - // above — avoids the pointer-events lockup when Leave - // channel's AlertDialog opens. - + diff --git a/desktop/tests/e2e/reminder-click-repro.spec.ts b/desktop/tests/e2e/reminder-click-repro.spec.ts new file mode 100644 index 0000000000..60f95f80e5 --- /dev/null +++ b/desktop/tests/e2e/reminder-click-repro.spec.ts @@ -0,0 +1,144 @@ +import { expect, test } from "@playwright/test"; + +import { waitForAnimations } from "../helpers/animations"; +import { installMockBridge } from "../helpers/bridge"; + +/** + * Regression suite for the "app freezes after setting a reminder" bug. + * + * The app used to bundle two copies of @radix-ui/react-dismissable-layer + * (react-menu resolved 1.1.11 while react-dialog resolved 1.1.15). Each copy + * keeps its own module-level `originalBodyPointerEvents`, so when a *modal* + * menu opened a *modal* dialog in the same React commit, the dialog's copy + * saved the menu's `pointer-events: none` as the "original" body style and + * restored it when the dialog closed — leaving `pointer-events: none` stuck + * on and deadening the whole app to the mouse. + * + * The inbox row context menu hit this: its "Remind me later" item opens + * RemindMeLaterDialog synchronously. Fixed by deduplicating the layer via + * the pnpm override in pnpm-workspace.yaml — a single copy coordinates + * nested modal layers correctly, so the menu keeps its default modal + * behavior. + * + * Each test drives one reminder entry point through a full set-reminder + * cycle, then asserts pointer-events is not stuck and that a real + * click still lands. The inbox right-click test fails if the dismissable + * layer ever duplicates again; the message-menu tests pin the already-safe + * paths. + */ + +const GENERAL_MESSAGE_ROW = "message-row"; +// The default mock feed's mention row (from alice, in #general) has a +// channel target, so its "Remind me later" context-menu item is enabled. +const INBOX_MENTION_ROW = "home-inbox-item-mock-feed-mention"; + +/** + * Assert the app survived the menu → dialog → close cycle. + * + * The stuck state is inline `pointer-events: none` left on . The buggy + * restore runs when the dialog's dismissable layer unmounts — after its exit + * animation — so first wait for the dialog to leave the DOM, then check the + * body style, then prove clicks still land by navigating via the sidebar. + */ +async function expectAppAliveAfterDialogClose( + page: import("@playwright/test").Page, +) { + await expect(page.getByRole("dialog")).toHaveCount(0); + await waitForAnimations(page); + expect(await page.evaluate(() => document.body.style.pointerEvents)).not.toBe( + "none", + ); + await page.getByTestId("channel-random").click(); + await expect(page.getByTestId("chat-title")).toHaveText("random"); +} + +/** Open the Remind-me-later dialog from a message row's More-actions menu. */ +async function openReminderDialogFromMessageMenu( + page: import("@playwright/test").Page, +) { + await page.goto("/"); + await page.getByTestId("channel-general").click(); + await expect(page.getByTestId("chat-title")).toHaveText("general"); + + const messageRow = page.getByTestId(GENERAL_MESSAGE_ROW).first(); + await messageRow.hover(); + await messageRow.getByRole("button", { name: "More actions" }).click(); + + const remindItem = page.getByRole("menuitem", { name: "Remind me later" }); + await expect(remindItem).toBeVisible(); + await waitForAnimations(page); + await remindItem.click(); + + const dialog = page.getByRole("dialog"); + await expect(dialog).toBeVisible(); + await waitForAnimations(page); + return dialog; +} + +async function expectReminderSetToast(page: import("@playwright/test").Page) { + await expect( + page.locator("[data-sonner-toast]").filter({ hasText: "Reminder set" }), + ).toBeVisible(); +} + +test.describe("reminder set → app stays clickable", () => { + test.beforeEach(async ({ page }) => { + await installMockBridge(page); + }); + + test("01 — message menu → time preset", async ({ page }) => { + const dialog = await openReminderDialogFromMessageMenu(page); + + await dialog.getByRole("button", { name: "In 30 minutes" }).click(); + + await expectReminderSetToast(page); + await expectAppAliveAfterDialogClose(page); + }); + + test("02 — message menu → custom time via Set reminder footer button", async ({ + page, + }) => { + const dialog = await openReminderDialogFromMessageMenu(page); + + // The custom timestamp must be strictly in the future or the Set + // reminder button stays disabled — pick tomorrow at 09:00 local time. + const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000); + const date = [ + tomorrow.getFullYear(), + String(tomorrow.getMonth() + 1).padStart(2, "0"), + String(tomorrow.getDate()).padStart(2, "0"), + ].join("-"); + await dialog.getByLabel("Reminder date").fill(date); + await dialog.getByLabel("Reminder time").fill("09:00"); + await dialog.getByRole("button", { name: "Set reminder" }).click(); + + await expectReminderSetToast(page); + await expectAppAliveAfterDialogClose(page); + }); + + // The regression path: only the inbox right-click stacks a modal + // ContextMenu under the modal dialog. Fails if two copies of the + // dismissable layer are ever bundled again — body pointer-events stays + // "none" after the dialog closes and the sidebar click below times out. + test("03 — inbox row right-click → Remind me later", async ({ page }) => { + await page.goto("/"); + await expect(page.getByTestId("home-inbox")).toBeVisible(); + + const row = page.getByTestId(INBOX_MENTION_ROW); + await expect(row).toBeVisible(); + await row.click({ button: "right" }); + + const remindItem = page.getByRole("menuitem", { name: "Remind me later" }); + await expect(remindItem).toBeVisible(); + await waitForAnimations(page); + await remindItem.click(); + + const dialog = page.getByRole("dialog"); + await expect(dialog).toBeVisible(); + await waitForAnimations(page); + await dialog.getByRole("button", { name: "In 30 minutes" }).click(); + + await expectReminderSetToast(page); + await expectAppAliveAfterDialogClose(page); + }); +}); diff --git a/desktop/tests/e2e/sidebar.spec.ts b/desktop/tests/e2e/sidebar.spec.ts index c712f145f2..f5ec16c537 100644 --- a/desktop/tests/e2e/sidebar.spec.ts +++ b/desktop/tests/e2e/sidebar.spec.ts @@ -22,10 +22,12 @@ async function storedSidebarWidth(page: Page) { ); } -// Regression guard for the "Leave channel" lockup: opening a modal AlertDialog -// from a modal Radix ContextMenu leaves `pointer-events: none` stuck on -// after the dialog closes, freezing the whole app. The fix makes the sidebar -// context menus non-modal. This asserts the app is still interactive. +// Regression guard for the "Leave channel" lockup: with two bundled copies of +// @radix-ui/react-dismissable-layer, opening a modal AlertDialog from a modal +// Radix ContextMenu left `pointer-events: none` stuck on after the +// dialog closed, freezing the whole app. Fixed by the pnpm override in +// pnpm-workspace.yaml deduplicating the layer. This asserts the app is still +// interactive. async function expectAppClickable(page: Page) { await expect .poll(() => diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8c2a308f93..6e78bf1925 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@radix-ui/react-dismissable-layer': 1.1.15 + patchedDependencies: isomorphic-git: e9b414a60d4cf1d8aa18f7a779483984e821989967c235662566e94ef0238d3f virtua@0.49.3: 367ec28b983b840021685fb5b515df23616a4d9b500614483ada43907c1e14e2 @@ -72,7 +75,7 @@ importers: version: 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-slot': specifier: ^1.2.4 - version: 1.2.5(@types/react@19.2.17)(react@19.2.7) + version: 1.3.0(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-switch': specifier: ^1.2.6 version: 1.2.6(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -250,7 +253,7 @@ importers: version: 4.6.2 '@radix-ui/react-slot': specifier: ^1.2.4 - version: 1.2.5(@types/react@19.2.17)(react@19.2.7) + version: 1.3.0(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-tooltip': specifier: ^1.2.8 version: 1.2.12(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -455,21 +458,18 @@ packages: engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - libc: [glibc] '@biomejs/cli-linux-x64-musl@2.4.16': resolution: {integrity: sha512-iHDS+MCM65DPqWGu+ECC3uoALyj2H7F4nVUPxIPjz/PIl94EUu+EDfGZDzFP+NY1EOPVt9NQvwFqq7HdMmowdg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - libc: [musl] '@biomejs/cli-linux-x64@2.4.16': resolution: {integrity: sha512-NbcBbi/nJqn5baae6wqRXdS7Gadf2uRpehSh6vMSYpG8OhkXl/Xg8aorWrJ+9VWqAT5ml90alLvorkpMW0nBwQ==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - libc: [glibc] '@biomejs/cli-win32-arm64@2.4.16': resolution: {integrity: sha512-0rgImMsNb5v/chhkIFe3wu7PEFClS6RBAYUijGL9UsYN3PanSaoK24HSSuSJb1pYbYYVjzAyZTl3gtjJ84BM8A==} @@ -523,15 +523,9 @@ packages: emoji-mart: ^5.2 react: ^16.8 || ^17 || ^18 - '@floating-ui/core@1.7.5': - resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} - '@floating-ui/core@1.8.0': resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} - '@floating-ui/dom@1.7.6': - resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} - '@floating-ui/dom@1.8.0': resolution: {integrity: sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==} @@ -541,9 +535,6 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' - '@floating-ui/utils@0.2.11': - resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} - '@floating-ui/utils@0.2.12': resolution: {integrity: sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==} @@ -792,19 +783,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-dismissable-layer@1.1.11': - resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - '@radix-ui/react-dismissable-layer@1.1.15': resolution: {integrity: sha512-b0XaRlzn2QKuo10XyNgi2DAJDf5XC9d1nD3FJcuvCjbR7+4Ad28zmZsLsqx+hvDEzMnRuZaZxZm9gYObV6RmRA==} peerDependencies: @@ -1221,15 +1199,6 @@ packages: '@types/react': optional: true - '@radix-ui/react-use-escape-keydown@1.1.1': - resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@radix-ui/react-use-is-hydrated@0.1.1': resolution: {integrity: sha512-qwOiz4Tjo8CNnrOLAYUMXeZwDzXgXpvK4TKQPmWLECM9XoWvA6+0Z2/7Ag3A4ivjS4ovbLJPbskkxioFyBhr8A==} peerDependencies: @@ -1365,21 +1334,18 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.2': resolution: {integrity: sha512-QVLO/czFMdoMFSqlX3bcswcJNm/23r+qoa/jgtmFc/qEp6/jXmIkDjF/XIo8dPfGaiwy1xfQn8o77L79GeXFgw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.2': resolution: {integrity: sha512-hgO5Abm0w5UL6FEa2iFnZqo2KlK7TQ5QhV5x09hujBf7t5KzHQ1VmfPuTpqRy/rNlSxua3eWH374xxiVrP+lcA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.2': resolution: {integrity: sha512-fy8rXxuYEu602abC8MUNaPjYLIFzReOaEIEMKMUa0rFEUxNpVXhs15KSSQ4qlqSaM7B6rcj9rDZgADh/IGDzLQ==} @@ -1393,7 +1359,6 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.2': resolution: {integrity: sha512-mjSkrzZK5Qsl0a9d1JgILOiuZOSDTVdKENcSXBoqbzSrspLR/4/IRVDo5wd2GgZjNss/viBFJdeq+j7qH2nypw==} @@ -1513,21 +1478,18 @@ packages: engines: {node: '>= 20'} cpu: [arm64] os: [linux] - libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.3.0': resolution: {integrity: sha512-DRNdQRpSGzRGfARVuVkxvM8Q12nh19l4BF/G7zGA1oe+9wcC6saFBHTISrpIcKzhiXtSrlSrluCfvMuledoCTQ==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.3.0': resolution: {integrity: sha512-Z0IADbDo8bh6I7h2IQMx601AdXBLfFpEdUotft86evd/8ZPflZe9COPO8Q1vw+pfLWIUo9zN/JGZvwuAJqduqg==} engines: {node: '>= 20'} cpu: [x64] os: [linux] - libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.3.0': resolution: {integrity: sha512-HNZGOUxEmElksYR7S6sC5jTeNGpobAsy9u7Gu0AskJ8/20FR9GqebUyB+HBcU/ax6BHuiuJi+Oda4B+YX6H1yA==} @@ -1665,7 +1627,6 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@tauri-apps/cli-linux-arm64-musl@2.11.2': resolution: {integrity: sha512-X1rm0BERqAAggtYTESSgXrS3sz4Sb/OiPiz54UqISlXW+GkR3vNIGnsy/lejNmoXGVqri3Q53BCfQiclOIyRPw==} @@ -1693,7 +1654,6 @@ packages: engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@tauri-apps/cli-win32-arm64-msvc@2.11.2': resolution: {integrity: sha512-HeeZW80jU+gVTOEX4X/hC6NVSAdDVXajwP5fxIZ/3z9WvUC7qrudX2GMTilYq6Dg0e0sk0XgsAJD1hZ5wPBXUA==} @@ -2403,7 +2363,6 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} @@ -2417,7 +2376,6 @@ packages: engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} @@ -3134,7 +3092,7 @@ packages: engines: {node: '>= 0.4'} wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + resolution: {integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=} yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -3349,21 +3307,10 @@ snapshots: emoji-mart: 5.6.0 react: 19.2.7 - '@floating-ui/core@1.7.5': - dependencies: - '@floating-ui/utils': 0.2.11 - optional: true - '@floating-ui/core@1.8.0': dependencies: '@floating-ui/utils': 0.2.12 - '@floating-ui/dom@1.7.6': - dependencies: - '@floating-ui/core': 1.7.5 - '@floating-ui/utils': 0.2.11 - optional: true - '@floating-ui/dom@1.8.0': dependencies: '@floating-ui/core': 1.8.0 @@ -3375,9 +3322,6 @@ snapshots: react: 19.2.7 react-dom: 19.2.7(react@19.2.7) - '@floating-ui/utils@0.2.11': - optional: true - '@floating-ui/utils@0.2.12': {} '@fontsource-variable/inter@5.2.8': {} @@ -3601,19 +3545,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - react-dom: 19.2.7(react@19.2.7) - optionalDependencies: - '@types/react': 19.2.17 - '@types/react-dom': 19.2.3(@types/react@19.2.17) - '@radix-ui/react-dismissable-layer@1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@radix-ui/primitive': 1.1.5 @@ -3697,7 +3628,7 @@ snapshots: '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-context': 1.1.2(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-direction': 1.1.1(@types/react@19.2.17)(react@19.2.7) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@radix-ui/react-dismissable-layer': 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-focus-guards': 1.1.3(@types/react@19.2.17)(react@19.2.7) '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@radix-ui/react-id': 1.1.1(@types/react@19.2.17)(react@19.2.7) @@ -4037,13 +3968,6 @@ snapshots: optionalDependencies: '@types/react': 19.2.17 - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@19.2.17)(react@19.2.7)': - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@19.2.17)(react@19.2.7) - react: 19.2.7 - optionalDependencies: - '@types/react': 19.2.17 - '@radix-ui/react-use-is-hydrated@0.1.1(@types/react@19.2.17)(react@19.2.7)': dependencies: react: 19.2.7 @@ -4464,7 +4388,7 @@ snapshots: '@tiptap/extension-bubble-menu@3.22.5(@tiptap/core@3.22.5(@tiptap/pm@3.22.5))(@tiptap/pm@3.22.5)': dependencies: - '@floating-ui/dom': 1.7.6 + '@floating-ui/dom': 1.8.0 '@tiptap/core': 3.22.5(@tiptap/pm@3.22.5) '@tiptap/pm': 3.22.5 optional: true diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4d1b29254b..865a41ad46 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -3,6 +3,14 @@ packages: - "web" allowBuilds: esbuild: true +overrides: + # Force a single copy of the dismissable-layer. Radix packages otherwise + # resolve different versions (react-menu pulled 1.1.11, react-dialog 1.1.15), + # and each copy keeps its own module-level saved pointer-events style — + # so a modal menu opening a modal dialog left `pointer-events: none` stuck on + # when the dialog closed, freezing the app (#1482, reminder dialog). + # Removable once every @radix-ui dep converges on one version naturally. + "@radix-ui/react-dismissable-layer": 1.1.15 patchedDependencies: isomorphic-git: patches/isomorphic-git.patch virtua@0.49.3: patches/virtua@0.49.3.patch