From 6607c333bc71089ac8a0e90d44b54edd5e1e19b1 Mon Sep 17 00:00:00 2001 From: sudacode Date: Fri, 31 Jul 2026 18:03:26 -0700 Subject: [PATCH] fix(overlay): strip spinner frame from subsync overlay card - Add overlayBody override to ConfiguredStatusNotificationOptions so overlay/OSD/desktop can diverge - Extract getSubsyncStatusNotificationOptions() to strip the ASCII spinner frame from the overlay card (OSD keeps it since it renders the raw spinner) - Add tests for spinner stripping and subsync result notifications --- .../configured-status-notification.test.ts | 59 +++++++++++++++++++ .../runtime/configured-status-notification.ts | 21 ++++++- .../runtime/overlay-notifications-runtime.ts | 11 +--- 3 files changed, 81 insertions(+), 10 deletions(-) diff --git a/src/main/runtime/configured-status-notification.test.ts b/src/main/runtime/configured-status-notification.test.ts index f88afadd..348c765c 100644 --- a/src/main/runtime/configured-status-notification.test.ts +++ b/src/main/runtime/configured-status-notification.test.ts @@ -2,6 +2,7 @@ import assert from 'node:assert/strict'; import test from 'node:test'; import { getPlaybackFeedbackNotificationOptions, + getSubsyncStatusNotificationOptions, getYoutubeFlowStatusNotificationOptions, notifyConfiguredStatus, } from './configured-status-notification'; @@ -158,6 +159,64 @@ test('notifyConfiguredStatus can suppress desktop delivery for progress ticks', assert.deepEqual(calls, ['overlay:subsync-status:Subsync:Subsync: syncing |:progress:pin']); }); +test('subsync progress keeps the osd spinner frame but strips it from the overlay card', () => { + const calls: string[] = []; + + for (const frame of ['|', '/', '-', '\\']) { + const message = `Subsync: syncing ${frame}`; + notifyConfiguredStatus( + message, + { + getNotificationType: () => 'both', + showOsd: (osdMessage) => { + calls.push(`osd:${osdMessage}`); + }, + showOverlayNotification: (payload) => + calls.push( + `overlay:${payload.body}:${payload.variant}:${payload.persistent ? 'pin' : 'auto'}`, + ), + showDesktopNotification: (title, options) => + calls.push(`desktop:${title}:${options.body ?? ''}`), + }, + getSubsyncStatusNotificationOptions(message), + ); + } + + assert.deepEqual(calls, [ + 'overlay:Subsync: syncing:progress:pin', + 'overlay:Subsync: syncing:progress:pin', + 'overlay:Subsync: syncing:progress:pin', + 'overlay:Subsync: syncing:progress:pin', + ]); + + calls.length = 0; + notifyConfiguredStatus( + 'Subsync: syncing /', + { + getNotificationType: () => 'osd', + showOsd: (osdMessage) => { + calls.push(`osd:${osdMessage}`); + }, + showOverlayNotification: (payload) => calls.push(`overlay:${payload.body}`), + showDesktopNotification: (title, options) => + calls.push(`desktop:${title}:${options.body ?? ''}`), + }, + getSubsyncStatusNotificationOptions('Subsync: syncing /'), + ); + + assert.deepEqual(calls, ['osd:Subsync: syncing /']); +}); + +test('subsync result notifications keep their message intact', () => { + assert.equal( + getSubsyncStatusNotificationOptions('Subtitle synchronized with ffsubsync').overlayBody, + 'Subtitle synchronized with ffsubsync', + ); + const failure = getSubsyncStatusNotificationOptions('ffsubsync synchronization failed: boom'); + assert.equal(failure.variant, 'error'); + assert.equal(failure.overlayBody, 'ffsubsync synchronization failed: boom'); +}); + test('notifyConfiguredStatus routes feedback through overlay without desktop delivery', () => { const calls: string[] = []; diff --git a/src/main/runtime/configured-status-notification.ts b/src/main/runtime/configured-status-notification.ts index 94b835c6..f694dc96 100644 --- a/src/main/runtime/configured-status-notification.ts +++ b/src/main/runtime/configured-status-notification.ts @@ -12,6 +12,8 @@ export interface ConfiguredStatusNotificationDeps { export interface ConfiguredStatusNotificationOptions { id?: string; + /** Overrides the overlay card body (the OSD/desktop paths keep the raw message). */ + overlayBody?: string; title?: string; variant?: OverlayNotificationPayload['variant']; persistent?: boolean; @@ -31,6 +33,23 @@ export function getPlaybackFeedbackNotificationOptions( return {}; } +export function getSubsyncStatusNotificationOptions( + message: string, +): ConfiguredStatusNotificationOptions { + const syncing = message.startsWith('Subsync: syncing'); + const failed = message.toLowerCase().includes('failed'); + return { + id: 'subsync-status', + title: 'Subsync', + // The overlay card renders its own animated spinner, so drop the ASCII + // spinner frame that the OSD path still needs. + overlayBody: syncing ? message.replace(/\s+[|/\-\\]$/, '') : message, + variant: failed ? 'error' : syncing ? 'progress' : 'info', + persistent: syncing, + desktop: !syncing, + }; +} + export function getYoutubeFlowStatusNotificationOptions( message: string, ): ConfiguredStatusNotificationOptions { @@ -74,7 +93,7 @@ export function notifyConfiguredStatus( deps.showOverlayNotification({ id: options.id, title: options.title ?? 'SubMiner', - body: message, + body: options.overlayBody ?? message, variant: options.variant ?? 'info', persistent: options.persistent ?? false, }); diff --git a/src/main/runtime/overlay-notifications-runtime.ts b/src/main/runtime/overlay-notifications-runtime.ts index 062ca74b..1dc01a97 100644 --- a/src/main/runtime/overlay-notifications-runtime.ts +++ b/src/main/runtime/overlay-notifications-runtime.ts @@ -22,6 +22,7 @@ import { withConfiguredOverlayNotificationPosition } from './overlay-notificatio import { createOverlayNotificationDelivery } from './overlay-notification-delivery'; import { getPlaybackFeedbackNotificationOptions, + getSubsyncStatusNotificationOptions, getYoutubeFlowStatusNotificationOptions, notifyConfiguredStatus, type ConfiguredStatusNotificationOptions, @@ -195,15 +196,7 @@ export function createOverlayNotificationsRuntime(deps: OverlayNotificationsRunt } function showSubsyncStatusNotification(message: string): void { - const syncing = message.startsWith('Subsync: syncing'); - const failed = message.toLowerCase().includes('failed'); - showConfiguredStatusNotification(message, { - id: 'subsync-status', - title: 'Subsync', - variant: failed ? 'error' : syncing ? 'progress' : 'info', - persistent: syncing, - desktop: !syncing, - }); + showConfiguredStatusNotification(message, getSubsyncStatusNotificationOptions(message)); } function showYoutubeFlowStatusNotification(message: string): void {