fix(startup): release autoplay gate before first subtitle line

- Send synthetic `__warm__` payload when no current subtitle exists so the gate can release without waiting for a subtitle event that can't fire while paused
- Visible-overlay readiness accepts `__warm__` once the overlay is content-ready, rejects it otherwise
- Autoplay gate self-retries via scheduled polling when signal target isn't ready, removing reliance on an external flush event
- Skip duplicate desktop notification when overlay or startup sequencer already delivered it
This commit is contained in:
2026-06-06 01:55:12 -07:00
parent ef914a321f
commit 501304e451
11 changed files with 205 additions and 22 deletions
@@ -4,6 +4,7 @@ import {
notifyCharacterDictionaryAutoSyncStatus,
type CharacterDictionaryAutoSyncNotificationEvent,
} from './character-dictionary-auto-sync-notifications';
import { createStartupOsdSequencer } from './startup-osd-sequencer';
function makeEvent(
phase: CharacterDictionaryAutoSyncNotificationEvent['phase'],
@@ -70,7 +71,7 @@ test('auto sync notifications send osd updates for progress phases', () => {
]);
});
test('auto sync notifications route both to overlay and system only', () => {
test('auto sync notifications prefer overlay delivery for both when overlay is available', () => {
const calls: string[] = [];
notifyCharacterDictionaryAutoSyncStatus(makeEvent('syncing', 'syncing'), {
@@ -100,9 +101,7 @@ test('auto sync notifications route both to overlay and system only', () => {
assert.deepEqual(calls, [
'overlay:character-dictionary-auto-sync:Character dictionary:syncing:pin',
'desktop:SubMiner:syncing',
'overlay:character-dictionary-auto-sync:Character dictionary:ready:auto',
'desktop:SubMiner:ready',
]);
});
@@ -187,3 +186,29 @@ test('auto sync notifications send osd-system desktop updates with startup seque
assert.deepEqual(calls, ['sequencer:importing:importing', 'desktop:SubMiner:importing']);
});
test('auto sync notifications let startup sequencer own osd-system desktop delivery', () => {
const calls: string[] = [];
const startupOsdSequencer = createStartupOsdSequencer({
getNotificationType: () => 'osd-system',
showOsd: (message) => {
calls.push(`osd:${message}`);
},
showDesktopNotification: (title, options) => {
calls.push(`desktop:${title}:${options.body ?? ''}`);
},
});
startupOsdSequencer.markTokenizationReady();
notifyCharacterDictionaryAutoSyncStatus(makeEvent('importing', 'importing'), {
getNotificationType: () => 'osd-system',
showOsd: (message) => {
calls.push(`direct-osd:${message}`);
},
showDesktopNotification: (title, options) =>
calls.push(`direct-desktop:${title}:${options.body ?? ''}`),
startupOsdSequencer,
});
assert.deepEqual(calls, ['osd:importing', 'desktop:SubMiner:importing']);
});