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

@@ -35,6 +35,8 @@ test('buildOverviewSummary aggregates tracked totals and recent windows', () =>
lookupCount: 10,
lookupHits: 8,
yomitanLookupCount: 0,
knownWordsSeen: 10,
knownWordRate: 12.5,
},
];
const rollups: DailyRollup[] = [
@@ -66,6 +68,8 @@ test('buildOverviewSummary aggregates tracked totals and recent windows', () =>
totalCards: 9,
totalLookupCount: 100,
totalLookupHits: 80,
totalTokensSeen: 1000,
totalYomitanLookupCount: 23,
newWordsToday: 5,
newWordsThisWeek: 20,
},
@@ -80,7 +84,10 @@ test('buildOverviewSummary aggregates tracked totals and recent windows', () =>
assert.equal(summary.allTimeMinutes, 50);
assert.equal(summary.activeDays, 2);
assert.equal(summary.totalSessions, 15);
assert.equal(summary.lookupRate, 80);
assert.deepEqual(summary.lookupRate, {
shortValue: '2.3 / 100 tokens',
longValue: '2.3 lookups per 100 tokens',
});
});
test('buildOverviewSummary prefers lifetime totals from hints when provided', () => {
@@ -104,6 +111,8 @@ test('buildOverviewSummary prefers lifetime totals from hints when provided', ()
lookupCount: 1,
lookupHits: 1,
yomitanLookupCount: 0,
knownWordsSeen: 2,
knownWordRate: 20,
},
],
rollups: [
@@ -132,6 +141,8 @@ test('buildOverviewSummary prefers lifetime totals from hints when provided', ()
totalCards: 5,
totalLookupCount: 0,
totalLookupHits: 0,
totalTokensSeen: 0,
totalYomitanLookupCount: 0,
newWordsToday: 0,
newWordsThisWeek: 0,
},
@@ -141,6 +152,7 @@ test('buildOverviewSummary prefers lifetime totals from hints when provided', ()
assert.equal(summary.totalTrackedCards, 5);
assert.equal(summary.allTimeMinutes, 120);
assert.equal(summary.activeDays, 40);
assert.equal(summary.lookupRate, null);
});
test('buildVocabularySummary treats firstSeen timestamps as seconds', () => {

View File

@@ -6,6 +6,7 @@ import type {
VocabularyEntry,
} from '../types/stats';
import { epochDayToDate, epochMsFromDbTimestamp, localDayFromMs } from './formatters';
import { buildLookupRateDisplay, type LookupRateDisplay } from './yomitan-lookup';
export interface ChartPoint {
label: string;
@@ -25,7 +26,7 @@ export interface OverviewSummary {
averageSessionMinutes: number;
activeDays: number;
totalSessions: number;
lookupRate: number | null;
lookupRate: LookupRateDisplay | null;
todayTokens: number;
newWordsToday: number;
newWordsThisWeek: number;
@@ -181,10 +182,10 @@ export function buildOverviewSummary(
: 0,
activeDays: overview.hints.activeDays ?? daysWithActivity.size,
totalSessions: overview.hints.totalSessions ?? overview.sessions.length,
lookupRate:
overview.hints.totalLookupCount > 0
? Math.round((overview.hints.totalLookupHits / overview.hints.totalLookupCount) * 100)
: null,
lookupRate: buildLookupRateDisplay(
overview.hints.totalYomitanLookupCount,
overview.hints.totalTokensSeen,
),
todayTokens: Math.max(
todayRow?.words ?? 0,
sumBy(todaySessions, (session) => session.tokensSeen),

View File

@@ -23,6 +23,8 @@ test('MediaSessionList renders expandable session rows with delete affordance',
lookupCount: 3,
lookupHits: 2,
yomitanLookupCount: 1,
knownWordsSeen: 6,
knownWordRate: 25,
},
]}
onDeleteSession={() => {}}

View File

@@ -1,7 +1,10 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import { renderToStaticMarkup } from 'react-dom/server';
import { SessionDetail } from '../components/sessions/SessionDetail';
import {
SessionDetail,
getKnownPctAxisMax,
} from '../components/sessions/SessionDetail';
import { buildSessionChartEvents } from './session-events';
import { EventType } from '../types/stats';
@@ -24,6 +27,8 @@ test('SessionDetail omits the misleading new words metric', () => {
lookupCount: 0,
lookupHits: 0,
yomitanLookupCount: 0,
knownWordsSeen: 0,
knownWordRate: 0,
}}
/>,
);
@@ -58,3 +63,11 @@ test('buildSessionChartEvents keeps only chart-relevant events and pairs pause r
);
assert.deepEqual(chartEvents.pauseRegions, [{ startMs: 2_000, endMs: 4_000 }]);
});
test('getKnownPctAxisMax adds headroom above the highest known percentage', () => {
assert.equal(getKnownPctAxisMax([22.4, 31.2, 29.8]), 40);
});
test('getKnownPctAxisMax caps the chart top at 100%', () => {
assert.equal(getKnownPctAxisMax([97.1, 98.6]), 100);
});

View File

@@ -157,6 +157,8 @@ test('SessionRow prefers token-based word count when available', () => {
lookupCount: 0,
lookupHits: 0,
yomitanLookupCount: 0,
knownWordsSeen: 0,
knownWordRate: 0,
}}
isExpanded={false}
detailsId="session-7"