Decouple stats daemon and preserve final mine OSD status

- Run `subminer stats -b` as a dedicated daemon process, independent from the overlay app
- Stop Anki progress spinner before showing final `✓`/`x` mine result so it is not overwritten
- Keep grammar/noise subtitle tokens hoverable while stripping annotation metadata
This commit is contained in:
2026-03-18 23:49:27 -07:00
parent 4d96ebf5c0
commit a954f62f55
32 changed files with 1879 additions and 78 deletions

View File

@@ -1,7 +1,7 @@
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { launchAppCommandDetached, runAppCommandAttached } from '../mpv.js';
import { runAppCommandAttached } from '../mpv.js';
import { sleep } from '../util.js';
import type { LauncherCommandContext } from './context.js';
@@ -20,12 +20,6 @@ type StatsCommandDeps = {
logLevel: LauncherCommandContext['args']['logLevel'],
label: string,
) => Promise<number>;
launchAppCommandDetached: (
appPath: string,
appArgs: string[],
logLevel: LauncherCommandContext['args']['logLevel'],
label: string,
) => void;
waitForStatsResponse: (responsePath: string) => Promise<StatsCommandResponse>;
removeDir: (targetPath: string) => void;
};
@@ -37,8 +31,6 @@ const defaultDeps: StatsCommandDeps = {
joinPath: (...parts) => path.join(...parts),
runAppCommandAttached: (appPath, appArgs, logLevel, label) =>
runAppCommandAttached(appPath, appArgs, logLevel, label),
launchAppCommandDetached: (appPath, appArgs, logLevel, label) =>
launchAppCommandDetached(appPath, appArgs, logLevel, label),
waitForStatsResponse: async (responsePath) => {
const deadline = Date.now() + STATS_STARTUP_RESPONSE_TIMEOUT_MS;
while (Date.now() < deadline) {
@@ -75,12 +67,15 @@ export async function runStatsCommand(
const responsePath = resolvedDeps.joinPath(tempDir, 'response.json');
try {
const forwarded = ['--stats', '--stats-response-path', responsePath];
if (args.statsBackground) {
forwarded.push('--stats-background');
}
if (args.statsStop) {
forwarded.push('--stats-stop');
const forwarded = args.statsCleanup
? ['--stats', '--stats-response-path', responsePath]
: [
args.statsStop ? '--stats-daemon-stop' : '--stats-daemon-start',
'--stats-response-path',
responsePath,
];
if (!args.statsCleanup && !args.statsBackground && !args.statsStop) {
forwarded.push('--stats-daemon-open-browser');
}
if (args.statsCleanup) {
forwarded.push('--stats-cleanup');
@@ -94,14 +89,6 @@ export async function runStatsCommand(
if (args.logLevel !== 'info') {
forwarded.push('--log-level', args.logLevel);
}
if (args.statsBackground) {
resolvedDeps.launchAppCommandDetached(appPath, forwarded, args.logLevel, 'stats');
const startupResult = await resolvedDeps.waitForStatsResponse(responsePath);
if (!startupResult.ok) {
throw new Error(startupResult.error || 'Stats dashboard failed to start.');
}
return true;
}
const attachedExitPromise = resolvedDeps.runAppCommandAttached(
appPath,
forwarded,