add app control server for launcher-to-app attachment

- Launcher detects a running app via control socket and attaches without spawning a new process
- Own-lifecycle app launches now pass --background --managed-playback; borrowed apps skip --background
- Separate plain subtitle websocket (tokens: []) from annotation websocket
- Default pauseVideoOnHover to true; update docs and config.example.jsonc
- Setup: remove plugin readiness card, add Open SubMiner Settings button
This commit is contained in:
2026-05-21 01:32:58 -07:00
parent 47f92129af
commit 355d7d95b2
58 changed files with 1618 additions and 205 deletions
+8
View File
@@ -156,3 +156,11 @@ test('discord presence update interval displays seconds while saving millisecond
assert.equal(toSettingsDisplayValue(path, 3000), 3);
assert.equal(toConfigDraftValue(path, 2.5), 2500);
});
test('websocket enabled select values save booleans instead of strings', () => {
assert.equal(toSettingsDisplayValue('websocket.enabled', true), 'true');
assert.equal(toSettingsDisplayValue('websocket.enabled', false), 'false');
assert.equal(toConfigDraftValue('websocket.enabled', 'true'), true);
assert.equal(toConfigDraftValue('websocket.enabled', 'false'), false);
assert.equal(toConfigDraftValue('websocket.enabled', 'auto'), 'auto');
});
+7
View File
@@ -75,6 +75,9 @@ export function toSettingsDisplayValue(
path: string,
value: ConfigSettingsSnapshotValue,
): ConfigSettingsSnapshotValue {
if (path === 'websocket.enabled' && typeof value === 'boolean') {
return value ? 'true' : 'false';
}
if (path === 'discordPresence.updateIntervalMs' && typeof value === 'number') {
return value / 1000;
}
@@ -85,6 +88,10 @@ export function toConfigDraftValue(
path: string,
value: ConfigSettingsSnapshotValue,
): ConfigSettingsSnapshotValue {
if (path === 'websocket.enabled') {
if (value === 'true') return true;
if (value === 'false') return false;
}
if (path === 'discordPresence.updateIntervalMs' && typeof value === 'number') {
return Math.round(value * 1000);
}