Fix stats command flow and tracking metrics regressions

- Route default `subminer stats` through attached `--stats`; keep daemon path for `--background`/`--stop`
- Update overview metrics: lookup rate uses lifetime Yomitan lookups per 100 tokens; new words dedupe by headword
- Suppress repeated macOS `Overlay loading...` OSD during fullscreen tracker flaps and improve session-detail chart scaling
- Add/adjust launcher, tracker query, stats server, IPC, overlay, and stats UI regression tests; add changelog fragments
This commit is contained in:
2026-03-19 15:46:52 -07:00
parent 274b0619ac
commit f2d6c70019
37 changed files with 1093 additions and 190 deletions

View File

@@ -4,6 +4,8 @@ import type { BaseWindowTracker } from '../window-trackers';
import type { WindowGeometry } from '../types';
import { updateVisibleOverlayVisibility } from '../core/services';
const OVERLAY_LOADING_OSD_COOLDOWN_MS = 30_000;
export interface OverlayVisibilityRuntimeDeps {
getMainWindow: () => BrowserWindow | null;
getVisibleOverlayVisible: () => boolean;
@@ -29,6 +31,8 @@ export interface OverlayVisibilityRuntimeService {
export function createOverlayVisibilityRuntimeService(
deps: OverlayVisibilityRuntimeDeps,
): OverlayVisibilityRuntimeService {
let lastOverlayLoadingOsdAtMs: number | null = null;
return {
updateVisibleOverlayVisibility(): void {
updateVisibleOverlayVisibility({
@@ -50,6 +54,15 @@ export function createOverlayVisibilityRuntimeService(
isMacOSPlatform: deps.isMacOSPlatform(),
isWindowsPlatform: deps.isWindowsPlatform(),
showOverlayLoadingOsd: (message: string) => deps.showOverlayLoadingOsd(message),
shouldShowOverlayLoadingOsd: () =>
lastOverlayLoadingOsdAtMs === null ||
Date.now() - lastOverlayLoadingOsdAtMs >= OVERLAY_LOADING_OSD_COOLDOWN_MS,
markOverlayLoadingOsdShown: () => {
lastOverlayLoadingOsdAtMs = Date.now();
},
resetOverlayLoadingOsdSuppression: () => {
lastOverlayLoadingOsdAtMs = null;
},
resolveFallbackBounds: () => deps.resolveFallbackBounds(),
});
},

View File

@@ -75,6 +75,29 @@ test('stats cli command starts tracker, server, browser, and writes success resp
]);
});
test('stats cli command respects stats.autoOpenBrowser=false', async () => {
const { handler, calls, responses } = makeHandler({
getResolvedConfig: () => ({
immersionTracking: { enabled: true },
stats: { serverPort: 6969, autoOpenBrowser: false },
}),
});
await handler({ statsResponsePath: '/tmp/subminer-stats-response.json' }, 'initial');
assert.deepEqual(calls, [
'ensureImmersionTrackerStarted',
'ensureStatsServerStarted',
'info:Stats dashboard available at http://127.0.0.1:6969',
]);
assert.deepEqual(responses, [
{
responsePath: '/tmp/subminer-stats-response.json',
payload: { ok: true, url: 'http://127.0.0.1:6969' },
},
]);
});
test('stats cli command starts background daemon without opening browser', async () => {
const { handler, calls, responses } = makeHandler({
ensureBackgroundStatsServerStarted: () => {