diff --git a/backlog/tasks/task-98 - Gate-subtitle-character-name-highlighting-on-character-dictionary-enablement.md b/backlog/tasks/task-98 - Gate-subtitle-character-name-highlighting-on-character-dictionary-enablement.md new file mode 100644 index 0000000..af1d56c --- /dev/null +++ b/backlog/tasks/task-98 - Gate-subtitle-character-name-highlighting-on-character-dictionary-enablement.md @@ -0,0 +1,60 @@ +--- +id: TASK-98 +title: Gate subtitle character-name highlighting on character dictionary enablement +status: Done +assignee: + - codex +created_date: '2026-03-07 00:54' +updated_date: '2026-03-07 00:56' +labels: + - subtitle + - character-dictionary +dependencies: [] +references: + - /Users/sudacode/projects/japanese/SubMiner/src/main.ts + - /Users/sudacode/projects/japanese/SubMiner/src/core/services/tokenizer.ts + - >- + /Users/sudacode/projects/japanese/SubMiner/src/config/definitions/defaults-subtitle.ts +priority: medium +--- + +## Description + + +Ensure subtitle tokenization and other annotations continue to work, but character-name lookup/highlighting is disabled whenever the AniList character dictionary feature is disabled. This avoids unnecessary name-match processing when the backing dictionary is unavailable. + + +## Acceptance Criteria + +- [x] #1 When anilist.characterDictionary.enabled is false, subtitle tokenization does not request character-name match metadata or highlight character names. +- [x] #2 When anilist.characterDictionary.enabled is true and subtitleStyle.nameMatchEnabled is true, existing character-name matching behavior remains enabled. +- [x] #3 Subtitle tokenization, JLPT, frequency, and other non-name annotation behavior remain unchanged when character dictionaries are disabled. +- [x] #4 Automated tests cover the runtime gating behavior. + + +## Implementation Plan + + +1. Add a failing test in `src/main/runtime/subtitle-tokenization-main-deps.test.ts` proving name-match enablement resolves to false when `anilist.characterDictionary.enabled` is false even if `subtitleStyle.nameMatchEnabled` is true. +2. Update `src/main/runtime/subtitle-tokenization-main-deps.ts` and `src/main.ts` so subtitle tokenization only enables name matching when both the subtitle setting and the character dictionary setting are enabled. +3. Run focused Bun tests for the updated runtime deps and subtitle processing seams. +4. If verification stays green, check off acceptance criteria and record the result. + +Implementation plan saved in `docs/plans/2026-03-06-character-name-gating.md`. + + +## Implementation Notes + + +Created plan doc `docs/plans/2026-03-06-character-name-gating.md` after user approved the narrow runtime-gating approach. Proceeding with TDD from the subtitle tokenization main-deps seam. + +Implemented the gate at the subtitle tokenization runtime-deps boundary so `getNameMatchEnabled` is false unless both `subtitleStyle.nameMatchEnabled` and `anilist.characterDictionary.enabled` are true. + +Verification: `bun test src/main/runtime/subtitle-tokenization-main-deps.test.ts`, `bun test src/core/services/subtitle-processing-controller.test.ts`, `bun run typecheck`. + + +## Final Summary + + +Character-name lookup/highlighting is now suppressed when the AniList character dictionary is disabled, while subtitle tokenization and other annotation paths remain active. Added focused runtime-deps coverage and wired the main runtime to pass the character-dictionary enabled flag into subtitle tokenization. + diff --git a/docs/plans/2026-03-06-character-name-gating.md b/docs/plans/2026-03-06-character-name-gating.md new file mode 100644 index 0000000..db7dc2c --- /dev/null +++ b/docs/plans/2026-03-06-character-name-gating.md @@ -0,0 +1,50 @@ +# Character Name Gating Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Disable subtitle character-name lookup/highlighting when the AniList character dictionary feature is disabled, while keeping tokenization and all other annotations working. + +**Architecture:** Gate `getNameMatchEnabled` at the runtime-deps boundary used by subtitle tokenization. Keep the tokenizer pipeline intact and only suppress character-name metadata requests when `anilist.characterDictionary.enabled` is false, regardless of `subtitleStyle.nameMatchEnabled`. + +**Tech Stack:** TypeScript, Bun test runner, Electron main/runtime wiring. + +--- + +### Task 1: Add runtime gating coverage + +**Files:** +- Modify: `src/main/runtime/subtitle-tokenization-main-deps.test.ts` + +**Step 1: Write the failing test** + +Add a test proving `getNameMatchEnabled()` resolves to `false` when `getCharacterDictionaryEnabled()` is `false` even if `getNameMatchEnabled()` is `true`. + +**Step 2: Run test to verify it fails** + +Run: `bun test src/main/runtime/subtitle-tokenization-main-deps.test.ts` +Expected: FAIL because the deps builder does not yet combine the two flags. + +### Task 2: Implement minimal runtime gate + +**Files:** +- Modify: `src/main/runtime/subtitle-tokenization-main-deps.ts` +- Modify: `src/main.ts` + +**Step 3: Write minimal implementation** + +Add `getCharacterDictionaryEnabled` to the main handler deps and make the built `getNameMatchEnabled` return true only when both the subtitle setting and the character dictionary setting are enabled. + +**Step 4: Run tests to verify green** + +Run: `bun test src/main/runtime/subtitle-tokenization-main-deps.test.ts` +Expected: PASS. + +### Task 3: Verify no regressions in related tokenization seams + +**Files:** +- Modify: none unless failures reveal drift + +**Step 5: Run focused verification** + +Run: `bun test src/core/services/subtitle-processing-controller.test.ts src/main/runtime/subtitle-tokenization-main-deps.test.ts` +Expected: PASS. diff --git a/src/main.ts b/src/main.ts index 33f870f..0dfc493 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2436,6 +2436,7 @@ const { 'subtitle.annotation.jlpt', getResolvedConfig().subtitleStyle.enableJlpt, ), + getCharacterDictionaryEnabled: () => getResolvedConfig().anilist.characterDictionary.enabled, getNameMatchEnabled: () => getResolvedConfig().subtitleStyle.nameMatchEnabled, getFrequencyDictionaryEnabled: () => getRuntimeBooleanOption( diff --git a/src/main/runtime/subtitle-tokenization-main-deps.test.ts b/src/main/runtime/subtitle-tokenization-main-deps.test.ts index f17fc84..64a74e6 100644 --- a/src/main/runtime/subtitle-tokenization-main-deps.test.ts +++ b/src/main/runtime/subtitle-tokenization-main-deps.test.ts @@ -54,6 +54,34 @@ test('tokenizer deps builder records known-word lookups and maps readers', () => assert.deepEqual(calls, ['lookup:true', 'lookup:false', 'set-window', 'set-ready', 'set-init']); }); +test('tokenizer deps builder disables name matching when character dictionary is disabled', () => { + const deps = createBuildTokenizerDepsMainHandler({ + getYomitanExt: () => null, + getYomitanParserWindow: () => null, + setYomitanParserWindow: () => undefined, + getYomitanParserReadyPromise: () => null, + setYomitanParserReadyPromise: () => undefined, + getYomitanParserInitPromise: () => null, + setYomitanParserInitPromise: () => undefined, + isKnownWord: () => false, + recordLookup: () => undefined, + getKnownWordMatchMode: () => 'surface', + getNPlusOneEnabled: () => true, + getMinSentenceWordsForNPlusOne: () => 3, + getJlptLevel: () => 'N2', + getJlptEnabled: () => true, + getCharacterDictionaryEnabled: () => false, + getNameMatchEnabled: () => true, + getFrequencyDictionaryEnabled: () => true, + getFrequencyDictionaryMatchMode: () => 'surface', + getFrequencyRank: () => 5, + getYomitanGroupDebugEnabled: () => false, + getMecabTokenizer: () => null, + })(); + + assert.equal(deps.getNameMatchEnabled?.(), false); +}); + test('mecab tokenizer check creates tokenizer once and runs availability check', async () => { const calls: string[] = []; type Tokenizer = { id: string }; diff --git a/src/main/runtime/subtitle-tokenization-main-deps.ts b/src/main/runtime/subtitle-tokenization-main-deps.ts index 3ee8c8d..b352180 100644 --- a/src/main/runtime/subtitle-tokenization-main-deps.ts +++ b/src/main/runtime/subtitle-tokenization-main-deps.ts @@ -2,6 +2,7 @@ import type { TokenizerDepsRuntimeOptions } from '../../core/services/tokenizer' type TokenizerMainDeps = TokenizerDepsRuntimeOptions & { getJlptEnabled: NonNullable; + getCharacterDictionaryEnabled?: () => boolean; getNameMatchEnabled?: NonNullable; getFrequencyDictionaryEnabled: NonNullable< TokenizerDepsRuntimeOptions['getFrequencyDictionaryEnabled'] @@ -46,7 +47,8 @@ export function createBuildTokenizerDepsMainHandler(deps: TokenizerMainDeps) { getJlptEnabled: () => deps.getJlptEnabled(), ...(deps.getNameMatchEnabled ? { - getNameMatchEnabled: () => deps.getNameMatchEnabled!(), + getNameMatchEnabled: () => + deps.getCharacterDictionaryEnabled?.() !== false && deps.getNameMatchEnabled!(), } : {}), getFrequencyDictionaryEnabled: () => deps.getFrequencyDictionaryEnabled(),