From 4ed878270fcac1d1800f19ec2b90cf354515c7b1 Mon Sep 17 00:00:00 2001 From: sudacode Date: Tue, 18 Aug 2026 02:24:49 -0700 Subject: [PATCH] fix(stats): accept legacy lexical rollup state schemas --- changes/stats-vocabulary-summary-totals.md | 2 +- .../immersion-tracker/lexical-rollups.test.ts | 21 +++++++++++++++++++ .../immersion-tracker/lexical-rollups.ts | 7 +++++-- 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/changes/stats-vocabulary-summary-totals.md b/changes/stats-vocabulary-summary-totals.md index 4b6f1346..4d48ddab 100644 --- a/changes/stats-vocabulary-summary-totals.md +++ b/changes/stats-vocabulary-summary-totals.md @@ -2,7 +2,7 @@ type: fixed area: stats - Fixed Vocabulary totals and charts counting only the first browsing page instead of all tracked vocabulary, without delaying the rest of the page. -- New-word history now uses permanent daily lexical rollups that apply the same vocabulary filters as the totals and normalize legacy second/millisecond timestamps; versioned background rebuilds repair existing history without dropping playback writes or clearing watch-time, activity, efficiency, and library charts. +- New-word history now uses permanent daily lexical rollups that apply the same vocabulary filters as the totals and normalize legacy second/millisecond timestamps; versioned background rebuilds repair existing history across legacy rollup-state schemas without dropping playback writes or clearing watch-time, activity, efficiency, and library charts. - Calendar-day chart labels now preserve the recorded local date in time zones west of UTC. - Vocabulary summary cards and charts refresh automatically after the word exclusion list changes, and failed or unfinished loads use bounded retries before showing an inline error with a Retry control. - Rapid exclusion edits no longer race each other; writes are sent in order so a slower earlier save cannot overwrite a newer list. diff --git a/src/core/services/immersion-tracker/lexical-rollups.test.ts b/src/core/services/immersion-tracker/lexical-rollups.test.ts index 9b5ac8e5..8fae14c8 100644 --- a/src/core/services/immersion-tracker/lexical-rollups.test.ts +++ b/src/core/services/immersion-tracker/lexical-rollups.test.ts @@ -257,6 +257,27 @@ test('legacy lexical rollup readiness does not satisfy the current rollup versio } }); +test('current lexical rollup readiness accepts legacy integer state storage', () => { + const dbPath = makeDbPath(); + const db = new Database(dbPath); + + try { + db.exec(` + CREATE TABLE imm_rollup_state( + state_key TEXT PRIMARY KEY, + state_value INTEGER NOT NULL + ); + INSERT INTO imm_rollup_state(state_key, state_value) + VALUES ('lexical_daily_rollups_version', 2); + `); + + assert.equal(areLexicalDailyRollupsReady(db), true); + } finally { + db.close(); + fs.rmSync(path.dirname(dbPath), { recursive: true, force: true }); + } +}); + test('imm_words persists vocabulary visibility for rollup maintenance', () => { const dbPath = makeDbPath(); const db = new Database(dbPath); diff --git a/src/core/services/immersion-tracker/lexical-rollups.ts b/src/core/services/immersion-tracker/lexical-rollups.ts index 62a2b592..ea5480a9 100644 --- a/src/core/services/immersion-tracker/lexical-rollups.ts +++ b/src/core/services/immersion-tracker/lexical-rollups.ts @@ -140,8 +140,11 @@ export function ensureLexicalDailyRollupTables(db: DatabaseSync): void { export function areLexicalDailyRollupsReady(db: DatabaseSync): boolean { const row = db .prepare(`SELECT state_value AS value FROM imm_rollup_state WHERE state_key = ?`) - .get(LEXICAL_DAILY_ROLLUP_VERSION_KEY) as { value: string } | null; - return row?.value === LEXICAL_DAILY_ROLLUP_VERSION; + .get(LEXICAL_DAILY_ROLLUP_VERSION_KEY) as { value: string | number } | undefined; + // Older databases created this column with INTEGER affinity, while current + // databases use TEXT. SQLite returns the same persisted version with a + // different JS type depending on that legacy schema. + return row !== undefined && String(row.value) === LEXICAL_DAILY_ROLLUP_VERSION; } export function markLexicalDailyRollupsReady(db: DatabaseSync): void {