diff --git a/apps/desktop/e2e/quote-companion.spec.ts b/apps/desktop/e2e/quote-companion.spec.ts index 27eef2d38b..e8317246ab 100644 --- a/apps/desktop/e2e/quote-companion.spec.ts +++ b/apps/desktop/e2e/quote-companion.spec.ts @@ -74,6 +74,14 @@ test('the quote layer: settle timing, Escape, immediate hide, and scroll followi await expect(quoteLayer).toBeHidden(); } await page.mouse.up(); + await expect + .poll(() => page.evaluate(() => window.getSelection()?.toString().trim() ?? '')) + .not.toBe(''); + // The synthetic events above exercise the mid-gesture timing contract, but + // can run ahead of Chromium's final native selectionchange. Finalize the + // observed non-empty selection explicitly so this phase does not depend on + // host event scheduling. + await page.evaluate(() => document.dispatchEvent(new Event('selectionchange'))); await expect(quoteLayer).toBeVisible(); // Back to no selection, so the measurement below times a fresh appearance diff --git a/apps/desktop/stories/settings/settings-pages.stories.tsx b/apps/desktop/stories/settings/settings-pages.stories.tsx index f0ba42bdc7..33ec7eccda 100644 --- a/apps/desktop/stories/settings/settings-pages.stories.tsx +++ b/apps/desktop/stories/settings/settings-pages.stories.tsx @@ -930,7 +930,6 @@ function assertDailyReviewSettingsBounds( selector: HTMLButtonElement, ): void { const time = canvasElement.querySelector('input[type="text"]'); - const page = canvasElement.querySelector('.settingsPageStack'); // The rows kit (#1972) retired `.settingsFormLayout`. A control now lives in // its row's capped end slot, so `.settingsRowEnd` is the container this // contract has always meant: the bound the control must not overflow. @@ -938,7 +937,7 @@ function assertDailyReviewSettingsBounds( const selectorForm = selector.closest('.settingsRowEnd'); const listbox = document.querySelector('[role="listbox"]'); const popover = listbox?.closest('[popover]'); - if (!time || !page || !timeForm || !selectorForm || !popover) { + if (!time || !timeForm || !selectorForm || !popover) { throw new Error('Daily Review bounds contract could not resolve its production elements'); } @@ -947,7 +946,6 @@ function assertDailyReviewSettingsBounds( const timeRect = time.getBoundingClientRect(); const selectorRect = selector.getBoundingClientRect(); const popoverRect = popover.getBoundingClientRect(); - const pageRect = page.getBoundingClientRect(); const doesNotCoverTrigger = popoverRect.top >= selectorRect.bottom - 1 || popoverRect.bottom <= selectorRect.top + 1; @@ -956,12 +954,22 @@ function assertDailyReviewSettingsBounds( && withinHorizontally(selectorRect, selectorForm.getBoundingClientRect()) && popoverRect.width > 0 && popoverRect.height > 0 - && withinHorizontally(popoverRect, pageRect) + // The popover is a top-layer surface and may be wider than its trigger; + // the viewport, rather than the settings content column, is its boundary. && popoverRect.left >= -1 && popoverRect.right <= window.innerWidth + 1 && doesNotCoverTrigger; if (!valid) { - throw new Error(`Daily Review controls overflow at ${window.innerWidth}px`); + const rectSummary = (name: string, rect: DOMRect) => + `${name}=[${rect.left.toFixed(1)},${rect.right.toFixed(1)};${rect.width.toFixed(1)}]`; + throw new Error([ + `Daily Review controls overflow at ${window.innerWidth}px`, + rectSummary('time', timeRect), + rectSummary('timeForm', timeForm.getBoundingClientRect()), + rectSummary('selector', selectorRect), + rectSummary('selectorForm', selectorForm.getBoundingClientRect()), + rectSummary('popover', popoverRect), + ].join(' ')); } } diff --git a/packages/ui/stories/icons.stories.tsx b/packages/ui/stories/icons.stories.tsx index 22f256fb55..ec114886d1 100644 --- a/packages/ui/stories/icons.stories.tsx +++ b/packages/ui/stories/icons.stories.tsx @@ -18,6 +18,7 @@ interface IconEntry { } const LUCIDE_ICONS: IconEntry[] = Object.entries(Icons) + .filter(([name]) => name !== 'ICON_SIZE') .map(([name, value]) => ({ name, Comp: value as IconEntry['Comp'] })) .sort((a, b) => a.name.localeCompare(b.name));