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
This commit is contained in:
2026-07-31 18:03:26 -07:00
parent b2bbf1ae12
commit 6607c333bc
3 changed files with 81 additions and 10 deletions
@@ -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[] = [];
@@ -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,
});
@@ -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 {