fix(stats): accept legacy lexical rollup state schemas

This commit is contained in:
2026-08-18 02:24:49 -07:00
parent 1d1b0c7bb6
commit 4ed878270f
3 changed files with 27 additions and 3 deletions
+1 -1
View File
@@ -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.
@@ -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);
@@ -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 {