Gate subtitle name highlighting on character dictionary setting

- Disable `getNameMatchEnabled` when `anilist.characterDictionary.enabled` is false
- Wire character-dictionary enablement into main subtitle tokenization deps
- Add runtime deps test coverage and record task/plan docs
This commit is contained in:
2026-03-06 17:13:56 -08:00
parent dbd6803623
commit 4d60f64bea
5 changed files with 142 additions and 1 deletions

View File

@@ -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
<!-- SECTION:DESCRIPTION:BEGIN -->
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.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [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.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
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`.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
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`.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
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.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -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.

View File

@@ -2436,6 +2436,7 @@ const {
'subtitle.annotation.jlpt', 'subtitle.annotation.jlpt',
getResolvedConfig().subtitleStyle.enableJlpt, getResolvedConfig().subtitleStyle.enableJlpt,
), ),
getCharacterDictionaryEnabled: () => getResolvedConfig().anilist.characterDictionary.enabled,
getNameMatchEnabled: () => getResolvedConfig().subtitleStyle.nameMatchEnabled, getNameMatchEnabled: () => getResolvedConfig().subtitleStyle.nameMatchEnabled,
getFrequencyDictionaryEnabled: () => getFrequencyDictionaryEnabled: () =>
getRuntimeBooleanOption( getRuntimeBooleanOption(

View File

@@ -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']); 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 () => { test('mecab tokenizer check creates tokenizer once and runs availability check', async () => {
const calls: string[] = []; const calls: string[] = [];
type Tokenizer = { id: string }; type Tokenizer = { id: string };

View File

@@ -2,6 +2,7 @@ import type { TokenizerDepsRuntimeOptions } from '../../core/services/tokenizer'
type TokenizerMainDeps = TokenizerDepsRuntimeOptions & { type TokenizerMainDeps = TokenizerDepsRuntimeOptions & {
getJlptEnabled: NonNullable<TokenizerDepsRuntimeOptions['getJlptEnabled']>; getJlptEnabled: NonNullable<TokenizerDepsRuntimeOptions['getJlptEnabled']>;
getCharacterDictionaryEnabled?: () => boolean;
getNameMatchEnabled?: NonNullable<TokenizerDepsRuntimeOptions['getNameMatchEnabled']>; getNameMatchEnabled?: NonNullable<TokenizerDepsRuntimeOptions['getNameMatchEnabled']>;
getFrequencyDictionaryEnabled: NonNullable< getFrequencyDictionaryEnabled: NonNullable<
TokenizerDepsRuntimeOptions['getFrequencyDictionaryEnabled'] TokenizerDepsRuntimeOptions['getFrequencyDictionaryEnabled']
@@ -46,7 +47,8 @@ export function createBuildTokenizerDepsMainHandler(deps: TokenizerMainDeps) {
getJlptEnabled: () => deps.getJlptEnabled(), getJlptEnabled: () => deps.getJlptEnabled(),
...(deps.getNameMatchEnabled ...(deps.getNameMatchEnabled
? { ? {
getNameMatchEnabled: () => deps.getNameMatchEnabled!(), getNameMatchEnabled: () =>
deps.getCharacterDictionaryEnabled?.() !== false && deps.getNameMatchEnabled!(),
} }
: {}), : {}),
getFrequencyDictionaryEnabled: () => deps.getFrequencyDictionaryEnabled(), getFrequencyDictionaryEnabled: () => deps.getFrequencyDictionaryEnabled(),