- 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
- Queue setExcludedWords writes so a slower in-flight save can't land after and overwrite a newer edit
- Run server-sync listeners independently so one throwing listener doesn't roll back a successful write or block the rest
- Add regression tests for write ordering, listener isolation, coalesced known-word snapshots, and vocabulary hook aggregate refresh/retry
- Refetch vocabulary summary/charts once the exclusion list write is server-acknowledged, and retry failed aggregate loads with backoff before showing an inline error with a Retry control
- Coalesce concurrent getVocabularySummary calls into a single worker task
- Scan imm_words in id-keyed batches to bound memory for large vocabularies
The query test suite opened each temp database with SQLite's defaults
(rollback journal, synchronous=FULL) while the runtime opens them via
applyPragmas (WAL, synchronous=NORMAL). Every statement therefore ran as
its own fsync-ing transaction.
That is unnoticeable on a fast local disk but dominates on CI: the file
took 40.6s there versus 0.5s locally, and the 106-insert case
'getVocabularyStats pages past hidden rows' crossed the 5s per-test
timeout and failed the quality gate.
Open test databases through a helper that applies the same pragmas as
production, so the tests exercise the runtime's actual SQLite
configuration instead of a slower one.
- Add allowReadingOnlyMatch:false to kanji token known-word lookups so 渓谷/けいこく no longer matches a mined 警告/けいこく card via reading-only index
- Greedy name pre-pass yields when a strictly longer generic word starts at the same position (空 no longer splits 空気; ties still go to the name)
- 確かに and やはり no longer excluded from frequency/JLPT highlighting and vocab stats
- Stop-word list now covers only interjections, pronouns, and grammar fragments