feat: add mark-watched action, background app reuse, and N+1 compat

- Add `--mark-watched` CLI flag + mpv session binding; marks video watched, shows OSD, advances playlist
- Launcher detects running background app via `--app-ping` and borrows it instead of owning its lifecycle
- Preserve N+1 highlighting for existing configs with `knownWords.highlightEnabled` set
- Fix `resolveConfiguredShortcuts` to respect explicit `null` overrides (disabling defaults)
- Split session-help modal into focused modules (colors, render, sections, tabs)
This commit is contained in:
2026-05-19 01:30:49 -07:00
parent 5c710ffcaf
commit 2772c61aba
42 changed files with 1429 additions and 505 deletions
+30
View File
@@ -18,6 +18,7 @@ type CompileSessionBindingsInput = {
keybindings: Keybinding[];
shortcuts: ConfiguredShortcuts;
statsToggleKey?: string | null;
statsMarkWatchedKey?: string | null;
platform: PlatformKeyModel;
rawConfig?: ResolvedConfig | null;
};
@@ -353,6 +354,8 @@ export function compileSessionBindings(input: CompileSessionBindingsInput): {
input.rawConfig?.shortcuts as Record<string, unknown> | undefined
)?.toggleVisibleOverlayGlobal;
const statsToggleKey = input.statsToggleKey ?? input.rawConfig?.stats?.toggleKey ?? null;
const statsMarkWatchedKey =
input.statsMarkWatchedKey ?? input.rawConfig?.stats?.markWatchedKey ?? null;
if (legacyToggleVisibleOverlayGlobal !== undefined) {
warnings.push({
@@ -419,6 +422,33 @@ export function compileSessionBindings(input: CompileSessionBindingsInput): {
}
}
if (statsMarkWatchedKey) {
const parsed = parseDomKeyString(statsMarkWatchedKey, input.platform);
if (!parsed.key) {
warnings.push({
kind: 'unsupported',
path: 'stats.markWatchedKey',
value: statsMarkWatchedKey,
message: parsed.message ?? 'Unsupported stats mark-watched key syntax.',
});
} else {
const binding: CompiledSessionActionBinding = {
sourcePath: 'stats.markWatchedKey',
originalKey: statsMarkWatchedKey,
key: parsed.key,
actionType: 'session-action',
actionId: 'markWatched',
};
const signature = getSessionKeySpecSignature(parsed.key);
const draft = candidates.get(signature) ?? [];
draft.push({
binding,
actionFingerprint: getBindingFingerprint(binding),
});
candidates.set(signature, draft);
}
}
input.keybindings.forEach((binding, index) => {
if (!binding.command) return;
const parsed = parseDomKeyString(binding.key, input.platform);