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()
This commit is contained in:
2026-06-05 12:58:02 -07:00
parent 486f682563
commit f675ef5b02
7 changed files with 29 additions and 29 deletions
+10
View File
@@ -88,6 +88,7 @@ export function updateVisibleOverlayVisibility(args: {
isMacOSPlatform?: boolean; isMacOSPlatform?: boolean;
isWindowsPlatform?: boolean; isWindowsPlatform?: boolean;
showOverlayLoadingOsd?: (message: string) => void; showOverlayLoadingOsd?: (message: string) => void;
dismissOverlayLoadingOsd?: () => void;
shouldShowOverlayLoadingOsd?: () => boolean; shouldShowOverlayLoadingOsd?: () => boolean;
markOverlayLoadingOsdShown?: () => void; markOverlayLoadingOsdShown?: () => void;
resetOverlayLoadingOsdSuppression?: () => void; resetOverlayLoadingOsdSuppression?: () => void;
@@ -320,6 +321,12 @@ export function updateVisibleOverlayVisibility(args: {
args.showOverlayLoadingOsd('Overlay loading...'); args.showOverlayLoadingOsd('Overlay loading...');
args.markOverlayLoadingOsdShown?.(); args.markOverlayLoadingOsdShown?.();
}; };
const maybeDismissOverlayLoadingOsd = (): void => {
if (!args.isMacOSPlatform) {
return;
}
args.dismissOverlayLoadingOsd?.();
};
const refreshNonNativeOverlayBoundsAfterFirstShow = (geometry: WindowGeometry | null): void => { const refreshNonNativeOverlayBoundsAfterFirstShow = (geometry: WindowGeometry | null): void => {
if ( if (
@@ -350,6 +357,7 @@ export function updateVisibleOverlayVisibility(args: {
if (!args.visibleOverlayVisible) { if (!args.visibleOverlayVisible) {
args.setTrackerNotReadyWarningShown(false); args.setTrackerNotReadyWarningShown(false);
args.resetOverlayLoadingOsdSuppression?.(); args.resetOverlayLoadingOsdSuppression?.();
maybeDismissOverlayLoadingOsd();
if (args.isWindowsPlatform) { if (args.isWindowsPlatform) {
clearPendingWindowsOverlayReveal(mainWindow); clearPendingWindowsOverlayReveal(mainWindow);
setOverlayWindowOpacity(mainWindow, 0); setOverlayWindowOpacity(mainWindow, 0);
@@ -372,6 +380,7 @@ export function updateVisibleOverlayVisibility(args: {
return; return;
} }
args.setTrackerNotReadyWarningShown(false); args.setTrackerNotReadyWarningShown(false);
maybeDismissOverlayLoadingOsd();
const geometry = args.windowTracker.getGeometry(); const geometry = args.windowTracker.getGeometry();
if (geometry) { if (geometry) {
args.updateVisibleOverlayBounds(geometry); args.updateVisibleOverlayBounds(geometry);
@@ -432,6 +441,7 @@ export function updateVisibleOverlayVisibility(args: {
(mainWindow.isVisible() || hasRetainedTrackedGeometry) (mainWindow.isVisible() || hasRetainedTrackedGeometry)
) { ) {
args.setTrackerNotReadyWarningShown(false); args.setTrackerNotReadyWarningShown(false);
maybeDismissOverlayLoadingOsd();
const geometry = args.windowTracker.getGeometry(); const geometry = args.windowTracker.getGeometry();
if (geometry) { if (geometry) {
args.updateVisibleOverlayBounds(geometry); args.updateVisibleOverlayBounds(geometry);
+3
View File
@@ -2654,6 +2654,9 @@ const overlayVisibilityRuntime = createOverlayVisibilityRuntimeService(
showOverlayLoadingOsd: (message: string) => { showOverlayLoadingOsd: (message: string) => {
showOverlayLoadingStatusNotification(message); showOverlayLoadingStatusNotification(message);
}, },
dismissOverlayLoadingOsd: () => {
dismissOverlayNotification('overlay-loading-status');
},
hideNonNativeOverlayWhenTargetUnfocused: () => hideNonNativeOverlayWhenTargetUnfocused: () =>
shouldRunLinuxOverlayZOrderKeepAlive() && shouldRunLinuxOverlayZOrderKeepAlive() &&
linuxVisibleOverlayWindowMode === 'fullscreen-override', linuxVisibleOverlayWindowMode === 'fullscreen-override',
+2
View File
@@ -30,6 +30,7 @@ export interface OverlayVisibilityRuntimeDeps {
isMacOSPlatform: () => boolean; isMacOSPlatform: () => boolean;
isWindowsPlatform: () => boolean; isWindowsPlatform: () => boolean;
showOverlayLoadingOsd: (message: string) => void; showOverlayLoadingOsd: (message: string) => void;
dismissOverlayLoadingOsd?: () => void;
resolveFallbackBounds: () => WindowGeometry; resolveFallbackBounds: () => WindowGeometry;
hideNonNativeOverlayWhenTargetUnfocused?: () => boolean; hideNonNativeOverlayWhenTargetUnfocused?: () => boolean;
} }
@@ -80,6 +81,7 @@ export function createOverlayVisibilityRuntimeService(
isMacOSPlatform: deps.isMacOSPlatform(), isMacOSPlatform: deps.isMacOSPlatform(),
isWindowsPlatform: deps.isWindowsPlatform(), isWindowsPlatform: deps.isWindowsPlatform(),
showOverlayLoadingOsd: (message: string) => deps.showOverlayLoadingOsd(message), showOverlayLoadingOsd: (message: string) => deps.showOverlayLoadingOsd(message),
dismissOverlayLoadingOsd: () => deps.dismissOverlayLoadingOsd?.(),
shouldShowOverlayLoadingOsd: () => shouldShowOverlayLoadingOsd: () =>
lastOverlayLoadingOsdAtMs === null || lastOverlayLoadingOsdAtMs === null ||
Date.now() - lastOverlayLoadingOsdAtMs >= OVERLAY_LOADING_OSD_COOLDOWN_MS, Date.now() - lastOverlayLoadingOsdAtMs >= OVERLAY_LOADING_OSD_COOLDOWN_MS,
@@ -36,6 +36,7 @@ test('overlay visibility runtime main deps builder maps state and geometry callb
isMacOSPlatform: () => true, isMacOSPlatform: () => true,
isWindowsPlatform: () => false, isWindowsPlatform: () => false,
showOverlayLoadingOsd: () => calls.push('overlay-loading-osd'), showOverlayLoadingOsd: () => calls.push('overlay-loading-osd'),
dismissOverlayLoadingOsd: () => calls.push('dismiss-overlay-loading-osd'),
resolveFallbackBounds: () => ({ x: 0, y: 0, width: 20, height: 20 }), 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.isMacOSPlatform(), true);
assert.equal(deps.isWindowsPlatform(), false); assert.equal(deps.isWindowsPlatform(), false);
deps.showOverlayLoadingOsd('Overlay loading...'); deps.showOverlayLoadingOsd('Overlay loading...');
deps.dismissOverlayLoadingOsd?.();
assert.deepEqual(deps.resolveFallbackBounds(), { x: 0, y: 0, width: 20, height: 20 }); assert.deepEqual(deps.resolveFallbackBounds(), { x: 0, y: 0, width: 20, height: 20 });
assert.equal(trackerNotReadyWarningShown, true); assert.equal(trackerNotReadyWarningShown, true);
assert.deepEqual(calls, [ assert.deepEqual(calls, [
@@ -71,5 +73,6 @@ test('overlay visibility runtime main deps builder maps state and geometry callb
'enforce-order', 'enforce-order',
'sync-shortcuts', 'sync-shortcuts',
'overlay-loading-osd', 'overlay-loading-osd',
'dismiss-overlay-loading-osd',
]); ]);
}); });
@@ -32,6 +32,7 @@ export function createBuildOverlayVisibilityRuntimeMainDepsHandler(
isMacOSPlatform: () => deps.isMacOSPlatform(), isMacOSPlatform: () => deps.isMacOSPlatform(),
isWindowsPlatform: () => deps.isWindowsPlatform(), isWindowsPlatform: () => deps.isWindowsPlatform(),
showOverlayLoadingOsd: (message: string) => deps.showOverlayLoadingOsd(message), showOverlayLoadingOsd: (message: string) => deps.showOverlayLoadingOsd(message),
dismissOverlayLoadingOsd: () => deps.dismissOverlayLoadingOsd?.(),
hideNonNativeOverlayWhenTargetUnfocused: () => hideNonNativeOverlayWhenTargetUnfocused: () =>
deps.hideNonNativeOverlayWhenTargetUnfocused?.() ?? false, deps.hideNonNativeOverlayWhenTargetUnfocused?.() ?? false,
resolveFallbackBounds: () => deps.resolveFallbackBounds(), resolveFallbackBounds: () => deps.resolveFallbackBounds(),
+2 -2
View File
@@ -85,7 +85,7 @@ function findChildByClass(element: FakeElement, className: string): FakeElement
} }
const overlayNotificationCss = readFileSync( const overlayNotificationCss = readFileSync(
path.join(process.cwd(), 'src/renderer/style.css'), path.join(__dirname, '..', 'renderer', 'style.css'),
'utf8', 'utf8',
); );
@@ -202,7 +202,7 @@ test('overlay notification cards use larger display dimensions', () => {
overlayNotificationCss, overlayNotificationCss,
/\.overlay-notification-stack\s*\{[^}]*width:\s*min\(420px,\s*calc\(100vw - 32px\)\);/s, /\.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( assert.match(
overlayNotificationCss, overlayNotificationCss,
/\.overlay-notification-card\.has-image\s*\{[^}]*min-height:\s*88px;/s, /\.overlay-notification-card\.has-image\s*\{[^}]*min-height:\s*88px;/s,
+8 -27
View File
@@ -176,26 +176,20 @@ body:focus-visible,
} }
.overlay-notification-card { .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); --overlay-notification-accent: var(--ctp-blue);
position: relative; position: relative;
display: grid; display: grid;
grid-template-columns: 22px minmax(0, 1fr) 22px; grid-template-columns: 22px minmax(0, 1fr) 22px;
gap: 11px; gap: 12px;
align-items: start; align-items: start;
min-height: 76px; min-height: 72px;
padding: 16px 16px 16px 20px; padding: 16px;
border-radius: 11px; border-radius: 12px;
border: 1px solid color-mix(in srgb, var(--overlay-notification-accent) 24%, var(--ctp-surface1)); border: 1px solid color-mix(in srgb, var(--overlay-notification-accent) 45%, var(--ctp-surface1));
background: linear-gradient( background: var(--ctp-base);
180deg, box-shadow: 0 12px 28px -12px rgba(24, 25, 38, 0.7);
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;
color: var(--ctp-text); color: var(--ctp-text);
overflow: hidden; overflow: hidden;
} }
@@ -226,18 +220,6 @@ body:focus-visible,
animation-name: overlay-notification-leave-top; 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-card.info {
--overlay-notification-accent: var(--ctp-blue); --overlay-notification-accent: var(--ctp-blue);
} }
@@ -264,7 +246,6 @@ body:focus-visible,
instead of letting it spill into the content column. */ instead of letting it spill into the content column. */
grid-template-columns: minmax(0, 100px) minmax(0, 1fr) 22px; grid-template-columns: minmax(0, 100px) minmax(0, 1fr) 22px;
min-height: 88px; min-height: 88px;
padding-left: 14px;
} }
.overlay-notification-image { .overlay-notification-image {