From 0f8370a3a96dc9251cbbaa77a4b81a07b329e20e Mon Sep 17 00:00:00 2001 From: sudacode Date: Fri, 5 Jun 2026 12:58:02 -0700 Subject: [PATCH] feat(notifications): auto-dismiss loading OSD on overlay visibility chan - add dismissOverlayLoadingOsd dep and call it on hide/show paths (macOS only) - simplify notification card styles: remove accent bar, flatten gradient bg, tweak spacing - fix test CSS path to use __dirname instead of process.cwd() --- src/core/services/overlay-visibility.ts | 10 ++++++ src/main.ts | 3 ++ src/main/overlay-visibility-runtime.ts | 2 ++ ...erlay-visibility-runtime-main-deps.test.ts | 3 ++ .../overlay-visibility-runtime-main-deps.ts | 1 + src/renderer/overlay-notifications.test.ts | 4 +-- src/renderer/style.css | 35 +++++-------------- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/core/services/overlay-visibility.ts b/src/core/services/overlay-visibility.ts index 7402a81f..2fec13e1 100644 --- a/src/core/services/overlay-visibility.ts +++ b/src/core/services/overlay-visibility.ts @@ -88,6 +88,7 @@ export function updateVisibleOverlayVisibility(args: { isMacOSPlatform?: boolean; isWindowsPlatform?: boolean; showOverlayLoadingOsd?: (message: string) => void; + dismissOverlayLoadingOsd?: () => void; shouldShowOverlayLoadingOsd?: () => boolean; markOverlayLoadingOsdShown?: () => void; resetOverlayLoadingOsdSuppression?: () => void; @@ -320,6 +321,12 @@ export function updateVisibleOverlayVisibility(args: { args.showOverlayLoadingOsd('Overlay loading...'); args.markOverlayLoadingOsdShown?.(); }; + const maybeDismissOverlayLoadingOsd = (): void => { + if (!args.isMacOSPlatform) { + return; + } + args.dismissOverlayLoadingOsd?.(); + }; const refreshNonNativeOverlayBoundsAfterFirstShow = (geometry: WindowGeometry | null): void => { if ( @@ -350,6 +357,7 @@ export function updateVisibleOverlayVisibility(args: { if (!args.visibleOverlayVisible) { args.setTrackerNotReadyWarningShown(false); args.resetOverlayLoadingOsdSuppression?.(); + maybeDismissOverlayLoadingOsd(); if (args.isWindowsPlatform) { clearPendingWindowsOverlayReveal(mainWindow); setOverlayWindowOpacity(mainWindow, 0); @@ -372,6 +380,7 @@ export function updateVisibleOverlayVisibility(args: { return; } args.setTrackerNotReadyWarningShown(false); + maybeDismissOverlayLoadingOsd(); const geometry = args.windowTracker.getGeometry(); if (geometry) { args.updateVisibleOverlayBounds(geometry); @@ -432,6 +441,7 @@ export function updateVisibleOverlayVisibility(args: { (mainWindow.isVisible() || hasRetainedTrackedGeometry) ) { args.setTrackerNotReadyWarningShown(false); + maybeDismissOverlayLoadingOsd(); const geometry = args.windowTracker.getGeometry(); if (geometry) { args.updateVisibleOverlayBounds(geometry); diff --git a/src/main.ts b/src/main.ts index 88d552ba..ab3b4e2a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2651,6 +2651,9 @@ const overlayVisibilityRuntime = createOverlayVisibilityRuntimeService( showOverlayLoadingOsd: (message: string) => { showOverlayLoadingStatusNotification(message); }, + dismissOverlayLoadingOsd: () => { + dismissOverlayNotification('overlay-loading-status'); + }, hideNonNativeOverlayWhenTargetUnfocused: () => shouldRunLinuxOverlayZOrderKeepAlive() && linuxVisibleOverlayWindowMode === 'fullscreen-override', diff --git a/src/main/overlay-visibility-runtime.ts b/src/main/overlay-visibility-runtime.ts index 9e68e9cd..1a35d503 100644 --- a/src/main/overlay-visibility-runtime.ts +++ b/src/main/overlay-visibility-runtime.ts @@ -30,6 +30,7 @@ export interface OverlayVisibilityRuntimeDeps { isMacOSPlatform: () => boolean; isWindowsPlatform: () => boolean; showOverlayLoadingOsd: (message: string) => void; + dismissOverlayLoadingOsd?: () => void; resolveFallbackBounds: () => WindowGeometry; hideNonNativeOverlayWhenTargetUnfocused?: () => boolean; } @@ -80,6 +81,7 @@ export function createOverlayVisibilityRuntimeService( isMacOSPlatform: deps.isMacOSPlatform(), isWindowsPlatform: deps.isWindowsPlatform(), showOverlayLoadingOsd: (message: string) => deps.showOverlayLoadingOsd(message), + dismissOverlayLoadingOsd: () => deps.dismissOverlayLoadingOsd?.(), shouldShowOverlayLoadingOsd: () => lastOverlayLoadingOsdAtMs === null || Date.now() - lastOverlayLoadingOsdAtMs >= OVERLAY_LOADING_OSD_COOLDOWN_MS, diff --git a/src/main/runtime/overlay-visibility-runtime-main-deps.test.ts b/src/main/runtime/overlay-visibility-runtime-main-deps.test.ts index fcef4244..e48a8f46 100644 --- a/src/main/runtime/overlay-visibility-runtime-main-deps.test.ts +++ b/src/main/runtime/overlay-visibility-runtime-main-deps.test.ts @@ -36,6 +36,7 @@ test('overlay visibility runtime main deps builder maps state and geometry callb isMacOSPlatform: () => true, isWindowsPlatform: () => false, showOverlayLoadingOsd: () => calls.push('overlay-loading-osd'), + dismissOverlayLoadingOsd: () => calls.push('dismiss-overlay-loading-osd'), resolveFallbackBounds: () => ({ x: 0, y: 0, width: 20, height: 20 }), })(); @@ -60,6 +61,7 @@ test('overlay visibility runtime main deps builder maps state and geometry callb assert.equal(deps.isMacOSPlatform(), true); assert.equal(deps.isWindowsPlatform(), false); deps.showOverlayLoadingOsd('Overlay loading...'); + deps.dismissOverlayLoadingOsd?.(); assert.deepEqual(deps.resolveFallbackBounds(), { x: 0, y: 0, width: 20, height: 20 }); assert.equal(trackerNotReadyWarningShown, true); assert.deepEqual(calls, [ @@ -71,5 +73,6 @@ test('overlay visibility runtime main deps builder maps state and geometry callb 'enforce-order', 'sync-shortcuts', 'overlay-loading-osd', + 'dismiss-overlay-loading-osd', ]); }); diff --git a/src/main/runtime/overlay-visibility-runtime-main-deps.ts b/src/main/runtime/overlay-visibility-runtime-main-deps.ts index e0486d28..63549e04 100644 --- a/src/main/runtime/overlay-visibility-runtime-main-deps.ts +++ b/src/main/runtime/overlay-visibility-runtime-main-deps.ts @@ -32,6 +32,7 @@ export function createBuildOverlayVisibilityRuntimeMainDepsHandler( isMacOSPlatform: () => deps.isMacOSPlatform(), isWindowsPlatform: () => deps.isWindowsPlatform(), showOverlayLoadingOsd: (message: string) => deps.showOverlayLoadingOsd(message), + dismissOverlayLoadingOsd: () => deps.dismissOverlayLoadingOsd?.(), hideNonNativeOverlayWhenTargetUnfocused: () => deps.hideNonNativeOverlayWhenTargetUnfocused?.() ?? false, resolveFallbackBounds: () => deps.resolveFallbackBounds(), diff --git a/src/renderer/overlay-notifications.test.ts b/src/renderer/overlay-notifications.test.ts index 20030558..f9262000 100644 --- a/src/renderer/overlay-notifications.test.ts +++ b/src/renderer/overlay-notifications.test.ts @@ -85,7 +85,7 @@ function findChildByClass(element: FakeElement, className: string): FakeElement } const overlayNotificationCss = readFileSync( - path.join(process.cwd(), 'src/renderer/style.css'), + path.join(__dirname, '..', 'renderer', 'style.css'), 'utf8', ); @@ -202,7 +202,7 @@ test('overlay notification cards use larger display dimensions', () => { overlayNotificationCss, /\.overlay-notification-stack\s*\{[^}]*width:\s*min\(420px,\s*calc\(100vw - 32px\)\);/s, ); - assert.match(overlayNotificationCss, /\.overlay-notification-card\s*\{[^}]*min-height:\s*76px;/s); + assert.match(overlayNotificationCss, /\.overlay-notification-card\s*\{[^}]*min-height:\s*72px;/s); assert.match( overlayNotificationCss, /\.overlay-notification-card\.has-image\s*\{[^}]*min-height:\s*88px;/s, diff --git a/src/renderer/style.css b/src/renderer/style.css index cd44d810..986cefc8 100644 --- a/src/renderer/style.css +++ b/src/renderer/style.css @@ -176,26 +176,20 @@ body:focus-visible, } .overlay-notification-card { - /* Accent color is overridden per variant and drives the bar, icon, and borders. */ + /* Accent color is overridden per variant and drives the icon and border tint. */ --overlay-notification-accent: var(--ctp-blue); position: relative; display: grid; grid-template-columns: 22px minmax(0, 1fr) 22px; - gap: 11px; + gap: 12px; align-items: start; - min-height: 76px; - padding: 16px 16px 16px 20px; - border-radius: 11px; - border: 1px solid color-mix(in srgb, var(--overlay-notification-accent) 24%, var(--ctp-surface1)); - background: linear-gradient( - 180deg, - color-mix(in srgb, var(--overlay-notification-accent) 9%, var(--ctp-surface0)), - var(--ctp-mantle) - ); - box-shadow: - 0 14px 32px -10px rgba(24, 25, 38, 0.75), - 0 1px 0 rgba(202, 211, 245, 0.05) inset; + min-height: 72px; + padding: 16px; + border-radius: 12px; + border: 1px solid color-mix(in srgb, var(--overlay-notification-accent) 45%, var(--ctp-surface1)); + background: var(--ctp-base); + box-shadow: 0 12px 28px -12px rgba(24, 25, 38, 0.7); color: var(--ctp-text); overflow: hidden; } @@ -226,18 +220,6 @@ body:focus-visible, animation-name: overlay-notification-leave-top; } -.overlay-notification-card::before { - content: ''; - position: absolute; - left: 0; - top: 11px; - bottom: 11px; - width: 3px; - border-radius: 0 3px 3px 0; - background: var(--overlay-notification-accent); - box-shadow: 0 0 11px -1px var(--overlay-notification-accent); -} - .overlay-notification-card.info { --overlay-notification-accent: var(--ctp-blue); } @@ -264,7 +246,6 @@ body:focus-visible, instead of letting it spill into the content column. */ grid-template-columns: minmax(0, 100px) minmax(0, 1fr) 22px; min-height: 88px; - padding-left: 14px; } .overlay-notification-image {