feat(notifications): add overlay notifications with position config

- Add Catppuccin Macchiato overlay notification stack with 3s transient timeout
- Add `notifications.overlayPosition` config (top-left | top | top-right)
- Route startup tokenization and subtitle annotation status through configured surfaces
- Deduplicate rapid subtitle mode toggle notifications
- Change `both` to mean overlay + system; add `osd-system` as legacy alias for old behavior
- Keep `osd`/`osd-system` as config-file-only legacy values; Settings UI offers overlay/system/both/none
This commit is contained in:
2026-06-04 21:56:51 -07:00
parent c09d009a3e
commit 144373db52
82 changed files with 2290 additions and 243 deletions
@@ -45,6 +45,7 @@ function createContext(overrides: Partial<LauncherCommandContext> = {}): Launche
autoStart: true,
autoStartVisibleOverlay: true,
autoStartPauseUntilReady: true,
osdMessages: false,
texthookerEnabled: false,
},
appPath: '/tmp/subminer.app',
@@ -82,6 +82,7 @@ function createContext(): LauncherCommandContext {
autoStart: true,
autoStartVisibleOverlay: true,
autoStartPauseUntilReady: true,
osdMessages: false,
texthookerEnabled: false,
},
appPath: '/tmp/SubMiner.AppImage',
@@ -207,6 +208,7 @@ test('plugin auto-start playback leaves app lifetime to managed-playback owner',
autoStart: true,
autoStartVisibleOverlay: false,
autoStartPauseUntilReady: false,
osdMessages: false,
texthookerEnabled: false,
};
const appPath = context.appPath ?? '';
@@ -268,6 +270,7 @@ test('plugin auto-start playback attaches a warm background app through the laun
autoStart: true,
autoStartVisibleOverlay: true,
autoStartPauseUntilReady: true,
osdMessages: false,
texthookerEnabled: true,
};
const calls: string[] = [];
@@ -335,6 +338,7 @@ test('plugin auto-start attach mode reuses launcher-resolved config dir for app
autoStart: true,
autoStartVisibleOverlay: true,
autoStartPauseUntilReady: true,
osdMessages: false,
texthookerEnabled: true,
};
let availabilityConfigDir: string | undefined;
@@ -395,6 +399,7 @@ test('plugin auto-start attach mode omits texthooker flag when CLI texthooker is
autoStart: true,
autoStartVisibleOverlay: true,
autoStartPauseUntilReady: true,
osdMessages: false,
texthookerEnabled: true,
};
const calls: string[] = [];
+23
View File
@@ -125,6 +125,11 @@ test('parseLauncherMpvConfig ignores invalid launch mode values', () => {
test('parsePluginRuntimeConfigFromMainConfig maps config.jsonc values over plugin defaults', () => {
const parsed = parsePluginRuntimeConfigFromMainConfig({
auto_start_overlay: false,
ankiConnect: {
behavior: {
notificationType: 'osd-system',
},
},
texthooker: {
launchAtStartup: false,
},
@@ -142,16 +147,30 @@ test('parsePluginRuntimeConfigFromMainConfig maps config.jsonc values over plugi
assert.equal(parsed.autoStart, true);
assert.equal(parsed.autoStartVisibleOverlay, false);
assert.equal(parsed.autoStartPauseUntilReady, true);
assert.equal(parsed.osdMessages, true);
assert.equal(parsed.binaryPath, '/opt/SubMiner/SubMiner.AppImage');
assert.equal(parsed.texthookerEnabled, false);
});
test('parsePluginRuntimeConfigFromMainConfig disables plugin osd messages for overlay notification routing', () => {
const parsed = parsePluginRuntimeConfigFromMainConfig({
ankiConnect: {
behavior: {
notificationType: 'both',
},
},
});
assert.equal(parsed.osdMessages, false);
});
test('parsePluginRuntimeConfigFromMainConfig defaults to background-only managed startup', () => {
const parsed = parsePluginRuntimeConfigFromMainConfig(null);
assert.equal(parsed.autoStart, true);
assert.equal(parsed.autoStartVisibleOverlay, false);
assert.equal(parsed.autoStartPauseUntilReady, true);
assert.equal(parsed.osdMessages, false);
assert.equal(parsed.texthookerEnabled, false);
});
@@ -165,6 +184,7 @@ test('buildPluginRuntimeScriptOptParts emits config values that override plugin
autoStart: true,
autoStartVisibleOverlay: false,
autoStartPauseUntilReady: true,
osdMessages: true,
texthookerEnabled: false,
},
'/fallback/SubMiner.AppImage',
@@ -176,6 +196,7 @@ test('buildPluginRuntimeScriptOptParts emits config values that override plugin
'subminer-auto_start=yes',
'subminer-auto_start_visible_overlay=no',
'subminer-auto_start_pause_until_ready=yes',
'subminer-osd_messages=yes',
'subminer-texthooker_enabled=no',
],
);
@@ -191,6 +212,7 @@ test('buildPluginRuntimeScriptOptParts strips script-option delimiters from stri
autoStart: true,
autoStartVisibleOverlay: false,
autoStartPauseUntilReady: true,
osdMessages: false,
texthookerEnabled: false,
},
'/fallback/SubMiner.AppImage',
@@ -202,6 +224,7 @@ test('buildPluginRuntimeScriptOptParts strips script-option delimiters from stri
'subminer-auto_start=yes',
'subminer-auto_start_visible_overlay=no',
'subminer-auto_start_pause_until_ready=yes',
'subminer-osd_messages=no',
'subminer-texthooker_enabled=no',
],
);
+5 -5
View File
@@ -16,10 +16,9 @@ function booleanOrDefault(value: unknown, fallback: boolean): boolean {
return typeof value === 'boolean' ? value : fallback;
}
function nonEmptyStringOrDefault(value: unknown, fallback: string): string {
if (typeof value !== 'string') return fallback;
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : fallback;
function pluginOsdMessagesFromNotificationType(root: Record<string, unknown> | null): boolean {
const notificationType = rootObject(rootObject(root, 'ankiConnect'), 'behavior').notificationType;
return notificationType === 'osd' || notificationType === 'osd-system';
}
function validBackendOrDefault(value: unknown, fallback: Backend): Backend {
@@ -53,6 +52,7 @@ export function parsePluginRuntimeConfigFromMainConfig(
autoStart: booleanOrDefault(mpvConfig.autoStartSubMiner, true),
autoStartVisibleOverlay: booleanOrDefault(root?.auto_start_overlay, false),
autoStartPauseUntilReady: booleanOrDefault(mpvConfig.pauseUntilOverlayReady, true),
osdMessages: pluginOsdMessagesFromNotificationType(root),
texthookerEnabled: booleanOrDefault(texthooker.launchAtStartup, false),
};
}
@@ -70,7 +70,7 @@ export function readPluginRuntimeConfig(logLevel: LogLevel): PluginRuntimeConfig
log(
'debug',
logLevel,
`Using mpv plugin settings from SubMiner config: socket_path=${parsed.socketPath}, backend=${parsed.backend}, auto_start=${parsed.autoStart}, auto_start_visible_overlay=${parsed.autoStartVisibleOverlay}, auto_start_pause_until_ready=${parsed.autoStartPauseUntilReady}, texthooker_enabled=${parsed.texthookerEnabled}`,
`Using mpv plugin settings from SubMiner config: socket_path=${parsed.socketPath}, backend=${parsed.backend}, auto_start=${parsed.autoStart}, auto_start_visible_overlay=${parsed.autoStartVisibleOverlay}, auto_start_pause_until_ready=${parsed.autoStartPauseUntilReady}, osd_messages=${parsed.osdMessages}, texthooker_enabled=${parsed.texthookerEnabled}`,
);
return parsed;
}
+1
View File
@@ -385,6 +385,7 @@ test('buildRuntimeExtraScriptOptParts marks launcher-owned startup pause gate',
autoStart: true,
autoStartVisibleOverlay: true,
autoStartPauseUntilReady: true,
osdMessages: false,
texthookerEnabled: false,
},
}),
+1
View File
@@ -207,6 +207,7 @@ export interface PluginRuntimeConfig {
autoStart: boolean;
autoStartVisibleOverlay: boolean;
autoStartPauseUntilReady: boolean;
osdMessages: boolean;
texthookerEnabled: boolean;
}