The cache manager and the stats server each carried a hand-written parser
for the same file. When the format went to v4 for maturity tiers only the
manager was updated, so loadKnownWordsSet fell through to null and every
session reported 0 known words from an intact cache.
Move the state union, its parser, and a knownWordsFromState accessor into
known-word-cache-format and have both readers go through it. The stats
server no longer mentions a version at all, and both version switches close
with assertNever, so adding a V5 is a compile error at every consumer
instead of a silently empty result. An unparseable cache now warns rather
than reading as "no known words".
Two stats fixtures omitted refreshedAtMs/scope and only passed because the
stats reader was laxer than the manager; they now carry the fields every
cache the app writes has always had.
Adds verify-known-word-highlights:electron, which tokenizes a subtitle file
through the real Yomitan/MeCab pipeline against the live known-word cache
and prints each cue in the configured tier colors, so highlighting can be
checked without stepping through playback.
--audit re-derives every rendered tier from live Anki card data (notesInfo
plus cardsInfo intervals) and reports tokens whose color disagrees, which
catches stale cache entries and tier-classification bugs alike. --profile-copy
runs against a scratch Yomitan profile so the check works while SubMiner
holds the userData lock.
Exposes KnownWordCacheManager.getKnownWordMatchNoteIds and the annotation
stage's known-word text/reading resolvers so the audit can trace a rendered
tier back to the exact notes behind it.
The session help color legend showed the four tier rows whenever
maturityEnabled was on, even with known-word highlighting off, where no
token is ever tinted. Extract isKnownWordMaturityLegendEnabled so the
legend requires both toggles, matching getKnownWordMaturityEnabled.
The interval tier queries matched any card with prop:ivl>=1, but a lapsed
card keeps an interval of at least the lapse minimum, so relearning cards
were caught by the young query first and the learning tier was unreachable
in practice. Add -is:learn to the mature and young queries so the buckets
stay disjoint and match Anki's own card counts; a note with a mature card
alongside a relearning card still resolves to mature.
Bump KNOWN_WORD_MATURITY_RULES_VERSION and fold it into the cache scope key
so caches built under the old rules refetch instead of serving stale tiers.
- Add `ankiConnect.knownWords.maturityEnabled`/`matureThresholdDays` config resolution with validation, warnings, and fallback to defaults
- Session help color legend shows one row per maturity tier (new/learning/young/mature) when maturity coloring is on, instead of a single known-words swatch
- Update docs and changelog entry to describe the new legend behavior
- Check directory-scoped overrides before falling back to legacy season-key matches, so a same-directory scoped entry wins over a stale legacy fallback
- Add regression test covering same-directory override vs legacy fallback precedence
- Bump electron-builder/electron-updater/eslint and related transitive deps (bun.lock, package.json)
- Match/replace AniList overrides by directory scope instead of exact series key, so later episodes keep the override even when filename guesses differ
- Add tests covering differing-guess matches and same-directory override replacement
- Update docs-site/character-dictionary.md and add changelog entry
- Add maturityEnabled, matureThresholdDays, and per-tier maturity colors to Known Words subsection
- Order maturity color fields new -> learning -> young -> mature
- Route knownWordMaturityColors.* paths through the color control
- Add registry tests for subsection grouping and field ordering
- appendFromNoteInfo no longer overwrites a note's tracked tier with 'new' when the note already has one recorded
- add test covering re-mining a note that already has a mature/learning tier
- Catch errors from fetchKnownWordMaturityTierSets so a failed tier query no longer aborts the whole refresh
- Known words still cache; tiers just stay null until the next successful refresh
- Add regression test covering findNotes failure on a tier query
Color known-word subtitle highlights by Anki card maturity (new,
learning, young, mature) like asbplayer (#171). Notes are classified
server-side with Anki search filters (prop:ivl, is:learn) during the
known-word cache refresh, so no per-card data is fetched. A word's tier
is its most mature matching card/note, with the same reading-aware
matching as boolean known-word lookups.
- known-word cache v4 state persists per-note tiers; lifecycle key only
gains the maturity field while enabled so existing caches survive
- ankiConnect.knownWords.maturityEnabled + matureThresholdDays (21)
- subtitleStyle.knownWordMaturityColors with catppuccin defaults
- word-maturity-<tier> class rides on word-known so hover/selection
rules keep applying; falls back to knownWordColor without tier data
- runtime toggle subtitle.annotation.knownWords.maturityEnabled
- Add formatRofiPrompt to trim trailing whitespace and append a single space, keeping the prompt from running into the input field
- Apply it across all rofi -p usages in picker.ts and history-command.ts
- Add tests for formatRofiPrompt edge cases (empty/whitespace-only prompts)
- Extract buildHistoryEntryActions to build the series action menu, now including a previous-episode option alongside replay/next/browse/quit
- Update docs and changelog entry to describe the new option
- Add tests covering previous/replay/next ordering and omission when last watched file is missing
- AnkiConnect proxy now detects EADDRINUSE, logs a warning, and surfaces an overlay notification instead of crashing video startup
- Background AppImage bootstrap runs Chromium with in-process-gpu so no GPU child survives app.exit() and outlives the FUSE mount, fixing the remaining DrKonqi "Service Crash" case
- Add changelog fragment for the proxy fix; update the AppImage quit fragment with the GPU child root cause
- Merge the five separate sync change fragments (launcher command, UI window, close/quit fixes, active-session double-count, monthly rollup timezone) into a single changes/sync.md
- Fold kana-pos-content-classification.md into annotation-noise-rule-table.md
- Drop the now-stale Unreleased sections from CHANGELOG.md and docs-site/changelog.md
- Tweak tsukihime-english-subtitles.md wording
- Autoplay-ready gate now ignores untokenized subtitle payloads while tokenization warmup is pending, so playback doesn't resume before the first tokenized cue
- Wire isTokenizationReady dep through main.ts and update gate/wiring tests accordingly
- Drop stale release/release-notes.md, add changelog entry under changes/
Background AppImage launches executed directly from a FUSE mount owned by a
short-lived helper's AppImage runtime. On quit, the runtime unmounted the
squashfs while utility children (network service) were still mid-shutdown,
SIGBUSing them and triggering KDE Service Crash notifications on every video
close. Detach a POSIX-sh supervisor instead: mount via --appimage-mount, run
AppRun from the mount, and release the holder only once no process still
executes from the mount. Falls back to the old direct launch on any failure
or with SUBMINER_NO_APPIMAGE_MOUNT_KEEPALIVE=1.
- Merge related fix entries into single files (name-match-span-merge into character-name-split-and-scan-recovery, kanji-non-independent-noun-annotations + lexicalized-expression-frequency into content-adverb-annotation-stop-list, greedy-name-parsing-priority into character-name-split-and-scan-recovery, kiku-field-grouping-cancel-double-toast into kiku-field-grouping-modal-recovery, history-rofi-cover-art into launcher-history-command)
- Rewrite remaining entries for brevity and reclassify stats-trend-title-limits as "changed"
- 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)