fix(stats): version lexical rollups and preserve backfill writes

- Persist vocabulary_visible on imm_words and filter rollups/triggers by it, matching the totals' exclusion rules
- Normalize legacy second/millisecond timestamps when bucketing rollup rows into local days
- Replace the boolean rollup-ready flag with a version key so schema changes trigger an atomic rebuild
- Preserve queued playback writes during rollup backfill instead of dropping them under queue pressure
- Time out the lexical rollup worker instead of hanging forever on no response
- Key concurrent vocabulary summary requests by their known-words snapshot so they no longer share stale state
- Bound stats dashboard retries for unfinished vocabulary loads before showing the inline Retry control
This commit is contained in:
2026-08-17 23:49:04 -07:00
parent e64763b487
commit 3b89886557
20 changed files with 506 additions and 93 deletions
@@ -18,6 +18,9 @@ export function createImmersionDbFixture(dbPath: string): void {
db.prepare(
`INSERT INTO imm_rollup_state(state_key, state_value) VALUES ('last_rollup_sample_ms', 0)`,
).run();
db.prepare(
`INSERT INTO imm_rollup_state(state_key, state_value) VALUES ('lexical_daily_rollups_version', 2)`,
).run();
db.prepare(
`INSERT INTO imm_lifetime_global(global_id, CREATED_DATE, LAST_UPDATE_DATE) VALUES (1, ?, ?)`,
).run(String(Date.now()), String(Date.now()));
@@ -154,6 +154,7 @@ export const IMMERSION_DB_FIXTURE_DDL = `
last_seen REAL,
frequency INTEGER,
frequency_rank INTEGER,
vocabulary_visible INTEGER NOT NULL DEFAULT 1 CHECK(vocabulary_visible IN (0, 1)),
UNIQUE(headword, word, reading)
);
CREATE TABLE imm_kanji(