Skip to content

feat(tokenizer): use Yomitan word classes for subtitle POS filtering#57

Merged
ksyasuda merged 42 commits into
mainfrom
tokenizer-updates
May 12, 2026
Merged

feat(tokenizer): use Yomitan word classes for subtitle POS filtering#57
ksyasuda merged 42 commits into
mainfrom
tokenizer-updates

Conversation

@ksyasuda

@ksyasuda ksyasuda commented Apr 26, 2026

Copy link
Copy Markdown
Owner

Summary

  • Carry matched headword wordClasses from Yomitan termsFind results through YomitanScanToken into MergedToken POS metadata — no vendored Yomitan changes required
  • Map recognized Yomitan word classes (prt, aux, v*, adj-i/ix, adj-na, noun-like) to SubMiner coarse PartOfSpeech/pos1 before annotation filtering
  • MeCab whole-line enrichment now only skips tokens with complete pos1+pos2+pos3; fills missing detail fields while preserving existing coarse pos1 set by Yomitan word class mapping
  • Expand subtitle annotation exclusion rules: standalone grammar particles (か/は/を/…), して helper fragments, single-kana surface fragments, もんか/ものか phrases, bare くれ, って, べき, たって
  • Preserve isKnown status on excluded tokens that have kanji surface/headword, clearing frequency/JLPT/N+1 metadata
  • Pass sourceText through annotation options to support sentence-boundary counting across Yomitan punctuation gaps (groundwork for TASK-304)
  • JLPT level cleared from standalone particle tokens (previously incorrectly retained)

Testing

  • bun test src/core/services/tokenizer/yomitan-parser-runtime.test.ts — new test: requestYomitanScanTokens preserves matched headword word classes
  • bun test src/core/services/tokenizer.test.ts — new tests: Yomitan word class → POS mapping, MeCab detail fill with coarse POS already set, known-word highlight for non-independent kanji nouns; updated particle JLPT test expectation
  • bun test src/core/services/tokenizer/annotation-stage.test.ts — new tests: して/particle/single-kana/もんか/くれ/って/べき exclusion, source-text sentence boundary gap, preserved isKnown for excluded kanji tokens
  • bun run typecheck — passed

Summary by CodeRabbit

  • New Features

    • Fullscreen reporting and Linux fullscreen-overlay refresh; Hyprland-aware window placement; stats window title/appearance updates; manual AniList post-watch trigger; Anki known-word update callback; refined numeric key selection and updated default shortcut; JLPT underline styling changes.
  • Bug Fixes

    • Improved subtitle tokenization: splits grammar endings, preserves JLPT/frequency/known-word flags, tightens N+1 targeting (excluding kana-only fragments). Fixed overlay pause/unpause, visibility, and shutdown cleanup ordering.
  • Tests

    • Expanded coverage across tokenization, MPV/overlay, Hyprland, keyboard/plugin bindings, AniList, Anki, stats, and lifecycle flows.

Review Change Stack

Checklist

  • Added a changelog fragment in changes/, or this PR is labeled skip-changelog

@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f43e9056-3a94-4b75-bde8-e656e32c2ed8

📥 Commits

Reviewing files that changed from the base of the PR and between ca796bf and e8f10fe.

⛔ Files ignored due to path filters (1)
  • backlog/tasks/task-347 - Address-PR-57-CodeRabbit-review-round-after-stats-session-fix.md is excluded by !backlog/**
📒 Files selected for processing (2)
  • src/core/services/overlay-window-input.ts
  • src/core/services/overlay-window.test.ts
✅ Files skipped from review due to trivial changes (1)
  • src/core/services/overlay-window.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/services/overlay-window-input.ts

📝 Walkthrough

Walkthrough

This PR updates tokenizer parsing/annotation (Yomitan wordClasses, grammar-ending splitting, POS coalescing, N+1), MPV fullscreen and Linux overlay refresh/cancellation, Hyprland placement, Anki known-word callbacks and card updates, overlay/stats window behavior, renderer input/plugin bindings, JLPT CSS, and many tests.

Changes

Tokenizer / Subtitle Annotation

Layer / File(s) Summary
Scan token shape & runtime
src/core/services/tokenizer/yomitan-parser-runtime.ts, src/core/services/tokenizer.test.ts
Add optional wordClasses?: string[]; validate/normalize and emit when non-empty; tests assert preservation and frequency selection when readings differ.
POS mapping & options
src/core/services/tokenizer.ts
Normalize wordClasses and map to { partOfSpeech, pos1? }; TokenizerAnnotationOptions gains sourceText passed into annotation/N+1 logic.
Parser selection / splitting
src/core/services/tokenizer/parser-selection-stage.ts, src/core/services/tokenizer/grammar-ending.ts
Detect standalone grammar-ending segments and emit them as separate merged tokens; updated merged-token emit/finalization.
Subtitle exclusion & stripping
src/core/services/tokenizer/subtitle-annotation-filter.ts, src/core/services/tokenizer/annotation-stage.ts
Add predicates for kana-only fragments, auxiliary/helper spans, suru-te helpers, standalone grammar particles, single-kana fragments; delegate stripping to shared filter; stripSubtitleAnnotationMetadata accepts options.
Enrichment coalescing
src/core/services/tokenizer/parser-enrichment-stage.ts
Per-field coalescing for pos1/pos2/pos3; only treat token complete when all three present; MeCab enrichment merges per-field.
Token merging / N+1 selection
src/token-merger.ts
Normalize sourceText for offsets; exclude kana-only tokens from N+1; detect sentence boundaries in source gaps; markNPlusOneTargets accepts sourceText.
Tests / Regression
many src/core/services/tokenizer* tests
Extensive updates: splitting, JLPT/frequency clearing, kana-only N+1 exclusions, merged/split scenarios, helper-span behavior, and POS wordClasses tests.

MPV fullscreen, Linux overlay refresh & lifecycle

Layer / File(s) Summary
MPV observation & protocol
src/core/services/mpv-properties.ts, src/core/services/mpv-protocol.ts
Observe fullscreen property; dispatchMpvProtocolMessage emits emitFullscreenChange({ fullscreen }) and updates time-pos handling order; tests added.
Client/event wiring
src/core/services/mpv.ts, src/main/runtime/mpv-client-event-bindings.ts
Add 'fullscreen-change' event and wire emitFullscreenChange through binder; client event emitted.
Linux refresh scheduler
src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
Linux-only cancellable refresh burst scheduled at multiple delays to refresh visible overlay (hide/show-inactive and reapply level); includes clear function and tests.
Lifecycle integration
src/main.ts, src/main/runtime/app-lifecycle-actions.ts
Store/cancel burst handles on MPV fullscreen changes and overlay hide/close; add cleanup step to clear Linux timeouts.
playNextSubtitle change
src/core/services/mpv.ts
Always set pendingPauseAtSubEnd, clear pauseAtTime, issue sub-seek, and conditionally unpause when appropriate; tests updated.

Hyprland placement & tracker

Layer / File(s) Summary
Placement utilities
src/core/services/hyprland-window-placement.ts
New helper: shouldAttempt, parse clients/monitors, find client by pid/title, build hyprctl dispatches (float/pin/move/resize), and ensure-floating entrypoint; tests added.
Tracker integration
src/window-trackers/hyprland-tracker.ts
Parse monitors, resolve fullscreen-aware geometry, use geometry-event matcher to schedule poll bursts, clear/unref timeouts, and update pollGeometry to use resolved dimensions; tests added.

Anki known-word cache & card update

Layer / File(s) Summary
Known-word cache
src/anki-integration/known-word-cache.ts
appendFromNoteInfo returns boolean indicating mutation, clears in-memory cache on state-key mismatch, persists updates; tests verify mutation reporting.
Anki integration
src/anki-integration.ts
Add setKnownWordCacheUpdatedCallback and notify when cache actually changed; tests added.
Card creation
src/anki-integration/card-creation.ts
Clipboard audio merge targets only sentence-only audio field (overwrite); tests adjusted.

Renderer input & plugin bindings

Layer / File(s) Summary
Keyboard numeric selection
src/renderer/handlers/keyboard.ts
Accept digits from e.key or e.code (Digit/Numpad); allow modifier-chord numeric starters; test added.
Mouse enter handling
src/renderer/handlers/mouse.ts
When suppressing direct hover-enter, consult provided MouseEvent coords via elementFromPoint before early-exit; tests updated.
Plugin session bindings
plugin/subminer/session_bindings.lua, scripts/test-plugin-session-bindings.lua, package.json
Detect shifted-letter Key[A-Z], build modifier-prefixes, register forced numeric bindings per prefix, add openCharacterDictionary CLI mapping, add Lua regression test and include it in plugin test script.

Overlay, Stats windows & options

Layer / File(s) Summary
Overlay options & enforcement
src/core/services/overlay-window-options.ts, src/core/services/overlay-window.ts
Centralize overlay titles, normalize bounds per-platform, change non-darwin path to visible-on-all-workspaces + Hyprland float + moveTop, ensure page title is enforced; tests updated.
Stats window
src/core/services/stats-window-runtime.ts, src/core/services/stats-window.ts
Add STATS_WINDOW_TITLE, compute outer window bounds from content bounds, change visuals (transparent:false, new background), ensure Hyprland float with bounds re-sync; tests added.

JLPT CSS & assorted runtime changes

Layer / File(s) Summary
JLPT styling
src/renderer/style.css, src/renderer/subtitle-render.test.ts, src/renderer/overlay-legacy-cleanup.test.ts
Replace text-decoration underlines with border-bottom, lock selection underline color via text-decoration-color !important, add :focus-visible outline suppression; tests updated.
Anilist setup & IPC
src/main/runtime/anilist-setup.ts, src/core/services/ipc.ts, src/main/runtime/anilist-post-watch.ts
saveToken now returns boolean and handler reacts accordingly; IPC deps accept optional runAnilistPostWatchUpdateOnManualMark; anilist post-watch runner accepts { force }; tests updated.
Startup/config & stats routing
src/main/runtime/startup-config.ts, src/main/runtime/stats-server-routing.ts
refreshAnilistClientSecretState now accepts allowSetupPrompt?: boolean; stats-server source renamed to background; tests adjusted.
Packaging/tests
package.json, stats/src/styles/globals.*
Include Lua plugin test in test:plugin:src, add stats CSS test to core tests, and many repository test updates.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch tokenizer-updates

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/core/services/tokenizer/parser-enrichment-stage.ts (1)

306-334: ⚠️ Potential issue | 🟠 Major

Treat blank POS fields as missing during backfill.

The new completeness check treats '' and whitespace-only POS fields as incomplete, but both fill paths still use ??. That means tokens with pos2: '' / pos3: '' will never be populated from MeCab and stay permanently half-enriched.

Suggested fix
+function coalesceMissingPosField(
+  current: string | undefined,
+  fallback: string | undefined,
+): string | undefined {
+  return typeof current === 'string' && current.trim().length > 0 ? current : fallback;
+}
+
 function fillMissingPos1BySurfaceSequence(
   tokens: MergedToken[],
   byTrimmedSurface: Map<string, IndexedMecabToken[]>,
 ): MergedToken[] {
@@
     return {
       ...token,
-      pos1: token.pos1 ?? best.pos1,
-      pos2: token.pos2 ?? best.pos2,
-      pos3: token.pos3 ?? best.pos3,
+      pos1: coalesceMissingPosField(token.pos1, best.pos1),
+      pos2: coalesceMissingPosField(token.pos2, best.pos2),
+      pos3: coalesceMissingPosField(token.pos3, best.pos3),
     };
   });
 }
@@
     return {
       ...token,
-      pos1: token.pos1 ?? metadata.pos1,
-      pos2: token.pos2 ?? metadata.pos2,
-      pos3: token.pos3 ?? metadata.pos3,
+      pos1: coalesceMissingPosField(token.pos1, metadata.pos1),
+      pos2: coalesceMissingPosField(token.pos2, metadata.pos2),
+      pos3: coalesceMissingPosField(token.pos3, metadata.pos3),
     };
   });

Also applies to: 387-417

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/tokenizer/parser-enrichment-stage.ts` around lines 306 -
334, The completeness check now treats whitespace-only POS fields as missing,
but the backfill still uses nullish coalescing (pos1: token.pos1 ?? best.pos1
etc.), so blank strings never get replaced; change each backfill to test for a
non-empty trimmed value (e.g., use token.pos1?.trim() ? token.pos1 : best.pos1)
for pos1, pos2, pos3 so whitespace/empty strings are replaced by MeCab values;
make the same change in the second analogous block (the other backfill around
the lowerBoundByIndex/cursor logic) so both enrichment paths use trimmed
emptiness checks instead of ??.
🧹 Nitpick comments (2)
src/core/services/tokenizer/annotation-stage.test.ts (1)

790-799: Optional: simplify this test fixture to only relevant deps.

isKnownWord / getJlptLevel stubs here are unrelated to the assertion and can be removed (or asserted) to keep the test intent tighter.

✂️ Suggested simplification
-  const result = annotateTokens(
-    tokens,
-    makeDeps({
-      isKnownWord: (text) => text === 'た' || text === '負',
-      getJlptLevel: (text) => (text === 'た' || text === '負' ? 'N3' : null),
-    }),
-    {
-      minSentenceWordsForNPlusOne: 1,
-    },
-  );
+  const result = annotateTokens(tokens, makeDeps(), {
+    minSentenceWordsForNPlusOne: 1,
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/tokenizer/annotation-stage.test.ts` around lines 790 - 799,
The test fixture passes extraneous stubs (isKnownWord/getJlptLevel) into
makeDeps when calling annotateTokens; simplify by removing those unused stubs
and only providing the minimal dependencies required for the assertion (or
replace them with noop/default implementations), keeping the annotateTokens call
and the minSentenceWordsForNPlusOne option intact; if you want to ensure they
aren't used, add an explicit assertion/mock that verifies
isKnownWord/getJlptLevel are not invoked rather than stubbing meaningful
behavior.
src/core/services/tokenizer/subtitle-annotation-filter.ts (1)

23-26: Optional: deduplicate shared standalone phrase literals.

たって / だって are maintained in multiple constants. Consider deriving both consumers from one source list to reduce drift risk.

♻️ Suggested refactor
+const STANDALONE_GRAMMAR_PARTICLE_PHRASES = new Set(['たって', 'だって']);
+
 const SUBTITLE_ANNOTATION_EXCLUDED_TERMS = new Set([
   'あ',
   'ああ',
   'ええ',
   'うう',
   'おお',
   'くれ',
-  'たって',
   'って',
-  'だって',
+  ...STANDALONE_GRAMMAR_PARTICLE_PHRASES,
   'はあ',
   'はは',
   'べき',
   'へえ',
   'ふう',
   'ほう',
   'もんか',
   'ものか',
 ]);
-const STANDALONE_GRAMMAR_PARTICLE_PHRASES = new Set(['たって', 'だって']);

Also applies to: 101-101

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/tokenizer/subtitle-annotation-filter.ts` around lines 23 -
26, Multiple literal standalone phrases like 'たって' and 'だって' are duplicated
across constants in subtitle-annotation-filter.ts; create a single shared
constant (e.g., SHARED_STANDALONE_PHRASES) containing those common entries and
replace the duplicated arrays referenced by the two consumers (the
constants/arrays that currently list 'たって'/'だって') so both derive from that one
source to avoid drift and ensure future updates stay in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/services/tokenizer.ts`:
- Around line 784-789: The code currently returns tokens produced by
parseWithYomitanInternalParser without calling stripSubtitleAnnotationMetadata
when applyAnnotationStage returns early, which lets subtitle-excluded tokens
keep annotation fields; update the no-annotation return path so that before
returning (after const yomitanTokens / when annotatedTokens may be skipped) you
call stripSubtitleAnnotationMetadata(yomitanTokens) and use that filtered array
in the returned object; ensure stripSubtitleAnnotationMetadata is invoked in the
branch that currently returns early from applyAnnotationStage and that the
returned object still uses annotationOptions.sourceText and other existing
fields unchanged.

---

Outside diff comments:
In `@src/core/services/tokenizer/parser-enrichment-stage.ts`:
- Around line 306-334: The completeness check now treats whitespace-only POS
fields as missing, but the backfill still uses nullish coalescing (pos1:
token.pos1 ?? best.pos1 etc.), so blank strings never get replaced; change each
backfill to test for a non-empty trimmed value (e.g., use token.pos1?.trim() ?
token.pos1 : best.pos1) for pos1, pos2, pos3 so whitespace/empty strings are
replaced by MeCab values; make the same change in the second analogous block
(the other backfill around the lowerBoundByIndex/cursor logic) so both
enrichment paths use trimmed emptiness checks instead of ??.

---

Nitpick comments:
In `@src/core/services/tokenizer/annotation-stage.test.ts`:
- Around line 790-799: The test fixture passes extraneous stubs
(isKnownWord/getJlptLevel) into makeDeps when calling annotateTokens; simplify
by removing those unused stubs and only providing the minimal dependencies
required for the assertion (or replace them with noop/default implementations),
keeping the annotateTokens call and the minSentenceWordsForNPlusOne option
intact; if you want to ensure they aren't used, add an explicit assertion/mock
that verifies isKnownWord/getJlptLevel are not invoked rather than stubbing
meaningful behavior.

In `@src/core/services/tokenizer/subtitle-annotation-filter.ts`:
- Around line 23-26: Multiple literal standalone phrases like 'たって' and 'だって'
are duplicated across constants in subtitle-annotation-filter.ts; create a
single shared constant (e.g., SHARED_STANDALONE_PHRASES) containing those common
entries and replace the duplicated arrays referenced by the two consumers (the
constants/arrays that currently list 'たって'/'だって') so both derive from that one
source to avoid drift and ensure future updates stay in sync.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 26d4b00d-d9bd-41b3-8054-dc6976da93e5

📥 Commits

Reviewing files that changed from the base of the PR and between d893464 and 6b7d055.

⛔ Files ignored due to path filters (2)
  • backlog/tasks/task-304 - Fix-N1-sentence-boundary-counting-across-Yomitan-punctuation-gaps.md is excluded by !backlog/**
  • backlog/tasks/task-305 - Use-Yomitan-word-classes-for-subtitle-token-POS-filtering.md is excluded by !backlog/**
📒 Files selected for processing (9)
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/token-merger.ts

Comment thread src/core/services/tokenizer.ts
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Apr 27, 2026

Copy link
Copy Markdown

Deploying subminer-docs with  Cloudflare Pages  Cloudflare Pages

Latest commit: 0b72fa1
Status: ✅  Deploy successful!
Preview URL: https://76bacc69.subminer.pages.dev
Branch Preview URL: https://tokenizer-updates.subminer.pages.dev

View logs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/core/services/tokenizer/annotation-stage.test.ts (1)

1073-1320: Recommend splitting this test file by concern.

Grouping exclusion predicates, metadata stripping, and N+1 behavior into separate suites/files would keep this TS module easier to evolve.

As per coding guidelines, "TypeScript files should be kept small and follow existing patterns".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/tokenizer/annotation-stage.test.ts` around lines 1073 -
1320, The test file currently mixes three concerns around annotateTokens
(exclusion predicates, metadata stripping, and N+1 behavior) which makes it
large and hard to maintain; split the suite into separate test modules (e.g.,
annotateTokens.exclusions.test.ts, annotateTokens.metadata.test.ts,
annotateTokens.nplusone.test.ts) by moving the relevant tests that reference
annotateTokens and makeDeps (the tests for standalone helpers/particles/phrases
that clear annotations, the tests asserting frequencyRank/jlptLevel cleared, and
the tests asserting isNPlusOneTarget behavior) into their respective files,
retain the same test names and assertions, update imports for annotateTokens,
makeDeps, makeToken, and PartOfSpeech in each new file, and run/update the test
runner configuration if needed so the new files are discovered.
src/core/services/tokenizer.test.ts (1)

3062-3249: Consider splitting this test file into focused suites.

This file now mixes parser selection, enrichment, exclusion, and N+1 behavior in one very large TS module. Splitting by concern would improve failure triage and long-term maintainability.

As per coding guidelines, "TypeScript files should be kept small and follow existing patterns".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/tokenizer.test.ts` around lines 3062 - 3249, The test file
mixes multiple concerns; split tests into focused suites by creating separate
test modules for parser selection (tests referencing tokenizeWithMecab
behavior), enrichment (tests asserting frequencyRank/jlptLevel like
"tokenizeSubtitle keeps frequency enrichment..."), exclusion/non-independent
pos2 behavior (the "excludes default non-independent pos2..." test), and
N+1/known-word behavior (tests using isKnownWord/getNPlusOneEnabled), moving the
corresponding test cases that call tokenizeSubtitle and use
makeDepsFromYomitanTokens into those files, export or import shared helpers
(makeDepsFromYomitanTokens, PartOfSpeech) so each new file can reuse them, keep
test names and assertions intact, and update any test runner imports/exports so
all new test files are discovered by the test suite.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main.ts`:
- Around line 1944-1958: The refreshLinuxVisibleOverlayAfterMpvFullscreenChange
function currently calls mainWindow.show() after hide(), which will steal focus
on Linux because the overlay is focusable; replace that call with
mainWindow.showInactive() so the overlay becomes visible without activating
input focus. Update the code in
refreshLinuxVisibleOverlayAfterMpvFullscreenChange (where it obtains mainWindow
via overlayManager.getMainWindow and calls ensureOverlayWindowLevel) to use
showInactive() instead of show(), keeping the preceding hide() and
ensureOverlayWindowLevel calls unchanged.

In `@src/token-merger.ts`:
- Around line 373-384: The gap-check is currently slicing the unnormalized
sourceText which can desynchronize offsets computed from the normalized text
(see markNPlusOneTargets); update hasSentenceBoundaryInSourceGap to normalize
sourceText with the same normalization routine used when computing token offsets
(the same function used around line 181) before creating gap =
sourceText.slice(previousEnd, nextStart), and apply the same
normalization-before-slicing change to the other gap-check sites referenced
(around lines 391 and 434-437) so all sentence-boundary checks operate on the
normalized text that matches the token offset space.

In `@src/window-trackers/hyprland-tracker.ts`:
- Around line 351-357: The getHyprlandMonitors function can throw when running
execSync('hyprctl -j monitors') or when parseHyprctlMonitors fails, causing
caller resolveHyprlandWindowGeometry to be unable to fall back to
client.at/client.size; wrap the execSync + parseHyprctlMonitors call in a
try/catch and return null on any error or parse failure so the caller can
degrade gracefully, referencing getHyprlandMonitors, execSync('hyprctl -j
monitors'), parseHyprctlMonitors, and
resolveHyprlandWindowGeometry/client.at/client.size.

---

Nitpick comments:
In `@src/core/services/tokenizer.test.ts`:
- Around line 3062-3249: The test file mixes multiple concerns; split tests into
focused suites by creating separate test modules for parser selection (tests
referencing tokenizeWithMecab behavior), enrichment (tests asserting
frequencyRank/jlptLevel like "tokenizeSubtitle keeps frequency enrichment..."),
exclusion/non-independent pos2 behavior (the "excludes default non-independent
pos2..." test), and N+1/known-word behavior (tests using
isKnownWord/getNPlusOneEnabled), moving the corresponding test cases that call
tokenizeSubtitle and use makeDepsFromYomitanTokens into those files, export or
import shared helpers (makeDepsFromYomitanTokens, PartOfSpeech) so each new file
can reuse them, keep test names and assertions intact, and update any test
runner imports/exports so all new test files are discovered by the test suite.

In `@src/core/services/tokenizer/annotation-stage.test.ts`:
- Around line 1073-1320: The test file currently mixes three concerns around
annotateTokens (exclusion predicates, metadata stripping, and N+1 behavior)
which makes it large and hard to maintain; split the suite into separate test
modules (e.g., annotateTokens.exclusions.test.ts,
annotateTokens.metadata.test.ts, annotateTokens.nplusone.test.ts) by moving the
relevant tests that reference annotateTokens and makeDeps (the tests for
standalone helpers/particles/phrases that clear annotations, the tests asserting
frequencyRank/jlptLevel cleared, and the tests asserting isNPlusOneTarget
behavior) into their respective files, retain the same test names and
assertions, update imports for annotateTokens, makeDeps, makeToken, and
PartOfSpeech in each new file, and run/update the test runner configuration if
needed so the new files are discovered.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 543913f2-92af-471b-991e-a5da46d21d75

📥 Commits

Reviewing files that changed from the base of the PR and between dc9d134 and bfa9b10.

⛔ Files ignored due to path filters (7)
  • backlog/tasks/task-306 - Fix-Hyprland-fullscreen-overlay-geometry-and-hover-pause.md is excluded by !backlog/**
  • backlog/tasks/task-307 - Exclude-kana-only-words-from-N1-subtitle-targets.md is excluded by !backlog/**
  • backlog/tasks/task-308 - Restore-persistent-JLPT-subtitle-underlines.md is excluded by !backlog/**
  • changes/306-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/307-kana-nplusone-targets.md is excluded by !changes/**
  • changes/308-jlpt-underlines.md is excluded by !changes/**
  • docs-site/troubleshooting.md is excluded by !docs-site/**
📒 Files selected for processing (21)
  • src/core/services/mpv-properties.ts
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/mpv-protocol.ts
  • src/core/services/mpv.test.ts
  • src/core/services/mpv.ts
  • src/core/services/overlay-window.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/main.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/renderer/handlers/mouse.test.ts
  • src/renderer/handlers/mouse.ts
  • src/renderer/style.css
  • src/renderer/subtitle-render.test.ts
  • src/token-merger.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/window-trackers/hyprland-tracker.ts
✅ Files skipped from review due to trivial changes (2)
  • src/core/services/mpv-properties.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts

Comment thread src/main.ts Outdated
Comment thread src/token-merger.ts
Comment thread src/window-trackers/hyprland-tracker.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/window-trackers/hyprland-tracker.ts (1)

304-313: Coalesce burst polling without a synchronous immediate call.

scheduleGeometryPollBurst() calls pollGeometry() inline, and pollGeometry() uses execSync. On noisy geometry events (windowtitle*, workspace*), this can block the event loop repeatedly. Consider scheduling 0ms instead of immediate execution so events are coalesced first.

Suggested diff
   private scheduleGeometryPollBurst(): void {
-    this.pollGeometry();
     for (const timeout of this.pollTimeouts) {
       clearTimeout(timeout);
     }
-    this.pollTimeouts = [50, 150, 300].map((delayMs) => {
+    this.pollTimeouts = [0, 50, 150, 300].map((delayMs) => {
       const pollTimeout = setTimeout(() => {
         this.pollTimeouts = this.pollTimeouts.filter((timeout) => timeout !== pollTimeout);
         this.pollGeometry();
       }, delayMs);
       return pollTimeout;

Also applies to: 321-324

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/window-trackers/hyprland-tracker.ts` around lines 304 - 313,
scheduleGeometryPollBurst currently calls pollGeometry synchronously (which uses
execSync) and can block the event loop on noisy events; change it to schedule an
immediate (setTimeout(..., 0)) call instead of invoking pollGeometry inline and
similarly replace any other direct pollGeometry calls in this function (and the
related block at 321-324) so the initial poll is queued and all bursts are
coalesced via pollTimeouts; update scheduleGeometryPollBurst to push a 0ms
timeout that calls pollGeometry and removes itself from this.pollTimeouts, and
ensure pollTimeouts is still cleared at the start of the method.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/window-trackers/hyprland-tracker.ts`:
- Around line 304-313: scheduleGeometryPollBurst currently calls pollGeometry
synchronously (which uses execSync) and can block the event loop on noisy
events; change it to schedule an immediate (setTimeout(..., 0)) call instead of
invoking pollGeometry inline and similarly replace any other direct pollGeometry
calls in this function (and the related block at 321-324) so the initial poll is
queued and all bursts are coalesced via pollTimeouts; update
scheduleGeometryPollBurst to push a 0ms timeout that calls pollGeometry and
removes itself from this.pollTimeouts, and ensure pollTimeouts is still cleared
at the start of the method.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 879ad861-b578-4566-98ff-6f7f41b0c593

📥 Commits

Reviewing files that changed from the base of the PR and between bfa9b10 and 7749c60.

📒 Files selected for processing (3)
  • src/main.ts
  • src/token-merger.ts
  • src/window-trackers/hyprland-tracker.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/token-merger.ts

@ksyasuda ksyasuda force-pushed the tokenizer-updates branch from 7749c60 to c150fce Compare April 27, 2026 08:34

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/main.ts (1)

1914-1978: Extract the Linux fullscreen refresh helper out of src/main.ts.

This logic is reasonable, but adding another platform-specific timer/burst helper to an already massive file makes src/main.ts harder to maintain. Moving the Linux fullscreen refresh constants and helpers into a small runtime module would better match the rest of the runtime composition pattern.

As per coding guidelines, TypeScript files should be kept small and follow existing patterns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 1914 - 1978, Extract the Linux fullscreen refresh
logic (const LINUX_MPV_FULLSCREEN_OVERLAY_REFRESH_DELAYS_MS, state var
linuxMpvFullscreenOverlayRefreshTimeouts, and the functions
clearLinuxMpvFullscreenOverlayRefreshTimeouts,
refreshLinuxVisibleOverlayAfterMpvFullscreenChange,
scheduleLinuxVisibleOverlayFullscreenRefreshBurst) into a new small runtime
module; move the constants/vars and functions out of src/main.ts, export the
functions, and update src/main.ts to import and call the exported schedule/clear
helpers. In the new module import any runtime dependencies used inside those
functions (overlayManager, overlayVisibilityRuntime, ensureOverlayWindowLevel)
from their existing modules, preserve the timeout unref() behavior and the same
filtering logic when clearing timeouts, and ensure types (ReturnType<typeof
setTimeout>) are kept so the new file compiles.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/services/tokenizer.ts`:
- Around line 355-364: The current check in
resolvePartOfSpeechFromYomitanWordClasses only matches the exact 'aux' token so
subclasses like 'aux-v' or 'aux-adj' fall through; update the auxiliary
detection to mirror the verb/noun pattern (e.g., use a startsWith/substring test
or any match that treats tokens beginning with 'aux' as auxiliary) and still
return { partOfSpeech: PartOfSpeech.bound_auxiliary, pos1: '助動詞' } so auxiliary
subclasses are correctly classified and retain subtitle exclusion and
JLPT/frequency suppression behavior.

---

Nitpick comments:
In `@src/main.ts`:
- Around line 1914-1978: Extract the Linux fullscreen refresh logic (const
LINUX_MPV_FULLSCREEN_OVERLAY_REFRESH_DELAYS_MS, state var
linuxMpvFullscreenOverlayRefreshTimeouts, and the functions
clearLinuxMpvFullscreenOverlayRefreshTimeouts,
refreshLinuxVisibleOverlayAfterMpvFullscreenChange,
scheduleLinuxVisibleOverlayFullscreenRefreshBurst) into a new small runtime
module; move the constants/vars and functions out of src/main.ts, export the
functions, and update src/main.ts to import and call the exported schedule/clear
helpers. In the new module import any runtime dependencies used inside those
functions (overlayManager, overlayVisibilityRuntime, ensureOverlayWindowLevel)
from their existing modules, preserve the timeout unref() behavior and the same
filtering logic when clearing timeouts, and ensure types (ReturnType<typeof
setTimeout>) are kept so the new file compiles.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: c7c11784-5070-4f43-b3f3-50f40f2db837

📥 Commits

Reviewing files that changed from the base of the PR and between 7749c60 and c150fce.

⛔ Files ignored due to path filters (10)
  • backlog/tasks/task-304 - Fix-N1-sentence-boundary-counting-across-Yomitan-punctuation-gaps.md is excluded by !backlog/**
  • backlog/tasks/task-305 - Use-Yomitan-word-classes-for-subtitle-token-POS-filtering.md is excluded by !backlog/**
  • backlog/tasks/task-306 - Fix-Hyprland-fullscreen-overlay-geometry-and-hover-pause.md is excluded by !backlog/**
  • backlog/tasks/task-307 - Exclude-kana-only-words-from-N1-subtitle-targets.md is excluded by !backlog/**
  • backlog/tasks/task-308 - Restore-persistent-JLPT-subtitle-underlines.md is excluded by !backlog/**
  • changes/305-tokenizer-word-class-pos-filtering.md is excluded by !changes/**
  • changes/306-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/307-kana-nplusone-targets.md is excluded by !changes/**
  • changes/308-jlpt-underlines.md is excluded by !changes/**
  • docs-site/troubleshooting.md is excluded by !docs-site/**
📒 Files selected for processing (28)
  • src/core/services/mpv-properties.ts
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/mpv-protocol.ts
  • src/core/services/mpv.test.ts
  • src/core/services/mpv.ts
  • src/core/services/overlay-window.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/main.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/renderer/handlers/mouse.test.ts
  • src/renderer/handlers/mouse.ts
  • src/renderer/style.css
  • src/renderer/subtitle-render.test.ts
  • src/token-merger.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/window-trackers/hyprland-tracker.ts
✅ Files skipped from review due to trivial changes (7)
  • src/core/services/mpv.test.ts
  • src/core/services/overlay-window.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/renderer/style.css
  • src/window-trackers/hyprland-tracker.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/renderer/handlers/mouse.test.ts
  • src/core/services/mpv-protocol.ts
  • src/renderer/handlers/mouse.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/token-merger.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/window-trackers/hyprland-tracker.test.ts

Comment thread src/core/services/tokenizer.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/core/services/tokenizer.test.ts (1)

3062-3790: Please split these new tokenizer scenarios into smaller test files.

This file is already very large; continuing to add dense blocks here makes navigation and maintenance harder. Consider extracting these additions into focused suites (for example: tokenizer.word-classes.test.ts and tokenizer.subtitle-exclusions.test.ts).

As per coding guidelines, TypeScript files should be kept small and follow existing patterns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/tokenizer.test.ts` around lines 3062 - 3790, The tokenizer
test additions (tests invoking tokenizeSubtitle with scenarios like "uses
Yomitan word classes..." "subtitle-exclusions", e.g. tests referencing
PartOfSpeech and helper makeDepsFromYomitanTokens/makeDeps) should be split into
smaller focused test files; create new suites such as
tokenizer.word-classes.test.ts (move tests about Yomitan wordClasses, auxiliary
subclasses, detailed MeCab POS) and tokenizer.subtitle-exclusions.test.ts (move
tests about frequency/N+1 exclusions, interjections, merged tokens, hoverable
grammar tails), copy the relevant tests (preserving describe/test names and any
helper usages of makeDeps/makeDepsFromYomitanTokens and PartOfSpeech), update
any shared helper imports so each new file imports the same helpers currently
used in tokenizer.test.ts, and remove the moved tests from the original file to
keep it small and consistent with existing test patterns.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main.ts`:
- Around line 3815-3824: The scheduled Linux fullscreen-refresh burst created
via scheduleLinuxVisibleOverlayFullscreenRefreshBurst (called from the
onFullscreenChange closure) can outlive the visible overlay and act on a
recreated window; fix by capturing the cancellation handle returned from
scheduleLinuxVisibleOverlayFullscreenRefreshBurst when calling it (from
onFullscreenChange) and invoke that cancel function whenever the visible overlay
is hidden or closed (e.g., in the overlay visibility hide/close handlers that
call into overlayManager / overlayVisibilityRuntime and any recreate path),
ensuring you also keep the existing quit-time clear; update the visible-overlay
hide/close code paths to call the stored cancel before destroying or replacing
the window so pending callbacks don’t run against the new window.

In `@src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts`:
- Around line 35-40: Replace the fixed 10ms sleep with a deterministic wait:
instead of awaiting new Promise(setTimeout(..., 10)), poll or await until the
expected callback has been invoked (e.g., wait in a loop with short yields until
calls.includes('updateVisibleOverlayVisibility') becomes true) or use controlled
timers (sinon/lolex fake timers) and advance them so the first setTimeout(…, 0)
runs; ensure the assertions then run after that awaited condition so the checks
for updateVisibleOverlayVisibility, hide, showInactive, and
ensureOverlayWindowLevel are stable.

---

Nitpick comments:
In `@src/core/services/tokenizer.test.ts`:
- Around line 3062-3790: The tokenizer test additions (tests invoking
tokenizeSubtitle with scenarios like "uses Yomitan word classes..."
"subtitle-exclusions", e.g. tests referencing PartOfSpeech and helper
makeDepsFromYomitanTokens/makeDeps) should be split into smaller focused test
files; create new suites such as tokenizer.word-classes.test.ts (move tests
about Yomitan wordClasses, auxiliary subclasses, detailed MeCab POS) and
tokenizer.subtitle-exclusions.test.ts (move tests about frequency/N+1
exclusions, interjections, merged tokens, hoverable grammar tails), copy the
relevant tests (preserving describe/test names and any helper usages of
makeDeps/makeDepsFromYomitanTokens and PartOfSpeech), update any shared helper
imports so each new file imports the same helpers currently used in
tokenizer.test.ts, and remove the moved tests from the original file to keep it
small and consistent with existing test patterns.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 590b81a7-2e46-4e08-a72f-21858ee25599

📥 Commits

Reviewing files that changed from the base of the PR and between c150fce and bacc90c.

⛔ Files ignored due to path filters (2)
  • backlog/tasks/task-309 - Accept-modified-follow-up-digits-for-multi-line-sentence-mining.md is excluded by !backlog/**
  • changes/309-multi-mine-modified-digits.md is excluded by !changes/**
📒 Files selected for processing (18)
  • package.json
  • plugin/subminer/session_bindings.lua
  • scripts/test-plugin-session-bindings.lua
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/main.ts
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/main/runtime/app-lifecycle-actions.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/renderer/handlers/keyboard.test.ts
  • src/renderer/handlers/keyboard.ts
  • src/token-merger.ts
  • src/window-trackers/hyprland-tracker.ts
✅ Files skipped from review due to trivial changes (2)
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • package.json
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/token-merger.ts
  • src/window-trackers/hyprland-tracker.ts
  • src/core/services/tokenizer/annotation-stage.test.ts

Comment thread src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
src/core/services/tokenizer/annotation-stage.test.ts (1)

369-493: Collapse these exclusion cases into table-driven assertions.

The coverage is solid, but both blocks repeat the same single-token setup and “all annotation fields cleared” assertions many times. A small helper plus subtests/data tables would keep this already-large file much easier to extend when more exclusions are added.

As per coding guidelines, "TypeScript files should be kept small and follow existing patterns".

Also applies to: 1207-1418

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/tokenizer/annotation-stage.test.ts` around lines 369 - 493,
The tests repeatedly construct single-token cases to assert
shouldExcludeTokenFromSubtitleAnnotations returns true; refactor by replacing
the repeated test blocks with a single table-driven test: create a data array of
token-parameter objects (using makeToken and PartOfSpeech values) and iterate
(or use test.each) to run a subtest per entry, asserting
shouldExcludeTokenFromSubtitleAnnotations(token) === true and including
token.surface as the subtest name; consolidate similar groups (e.g., standalone
particles, grammar helpers, single-kana) into grouped tables to keep the file
compact and follow existing patterns.
src/token-merger.ts (1)

286-327: Reuse the existing kana-normalization path for N+1 eligibility.

subtitle-annotation-filter.ts already defines similar kana-only exclusion behavior, but this new helper stack reimplements it on raw code points. That gives N+1 targeting a second “kana-only” definition and makes drift likely if the normalization rules change in one place but not the other. Please route this through the shared tokenizer helper, or normalize with the same path before checking here.

As per coding guidelines, "TypeScript files should be kept small and follow existing patterns".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/token-merger.ts` around lines 286 - 327, Replace the ad-hoc kana
detection in isKanaChar/isKanaCandidateIgnorableChar/isKanaOnlyText with the
shared tokenizer/normalization used by subtitle-annotation-filter.ts: import the
canonical normalization/tokenizer helper (the function used there to produce the
kana-normalized text) and call it from normalizeSourceTextForTokenOffsets or at
the top of isKanaOnlyText, then apply the existing kana-only check against that
normalized output instead of raw code points; update isKanaOnlyText to return
false on empty normalized result and keep the existing hasKana logic but
operating on the shared-normalized string so both modules use the same
normalization path and avoid drift.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/core/services/tokenizer/annotation-stage.test.ts`:
- Around line 369-493: The tests repeatedly construct single-token cases to
assert shouldExcludeTokenFromSubtitleAnnotations returns true; refactor by
replacing the repeated test blocks with a single table-driven test: create a
data array of token-parameter objects (using makeToken and PartOfSpeech values)
and iterate (or use test.each) to run a subtest per entry, asserting
shouldExcludeTokenFromSubtitleAnnotations(token) === true and including
token.surface as the subtest name; consolidate similar groups (e.g., standalone
particles, grammar helpers, single-kana) into grouped tables to keep the file
compact and follow existing patterns.

In `@src/token-merger.ts`:
- Around line 286-327: Replace the ad-hoc kana detection in
isKanaChar/isKanaCandidateIgnorableChar/isKanaOnlyText with the shared
tokenizer/normalization used by subtitle-annotation-filter.ts: import the
canonical normalization/tokenizer helper (the function used there to produce the
kana-normalized text) and call it from normalizeSourceTextForTokenOffsets or at
the top of isKanaOnlyText, then apply the existing kana-only check against that
normalized output instead of raw code points; update isKanaOnlyText to return
false on empty normalized result and keep the existing hasKana logic but
operating on the shared-normalized string so both modules use the same
normalization path and avoid drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 09925e0f-1aee-477d-87d8-47e87150e0cc

📥 Commits

Reviewing files that changed from the base of the PR and between bacc90c and 7442e42.

⛔ Files ignored due to path filters (1)
  • backlog/tasks/task-310 - Suppress-N1-highlight-for-kana-only-candidate-sentences.md is excluded by !backlog/**
📒 Files selected for processing (5)
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/main.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/token-merger.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/services/tokenizer/annotation-stage.test.ts`:
- Around line 672-709: The test uses a mixed-script surface "スイッチ…" (contains
punctuation) so it won't catch regressions in isKanaOnlyText; update the third
token created by makeToken in the test named "annotateTokens does not mark
kana-only unknown target with subtitle punctuation as N+1" to be truly kana-only
(e.g., change surface from "スイッチ…" to a pure-kana string like "スイッチ" or a
hiragana variant "すいっち" and ensure headword/reading match), so annotateTokens
and the isKanaOnlyText guard are exercised as intended.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d82198ce-4984-4044-86ea-04153e9ee2cf

📥 Commits

Reviewing files that changed from the base of the PR and between bacc90c and 251b6cd.

⛔ Files ignored due to path filters (5)
  • backlog/tasks/task-310 - Suppress-N1-highlight-for-kana-only-candidate-sentences.md is excluded by !backlog/**
  • backlog/tasks/task-311 - Suppress-auxiliary-inflection-fragments-from-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-312 - Suppress-ja-nai-explanatory-ending-subtitle-annotations.md is excluded by !backlog/**
  • changes/311-auxiliary-inflection-annotation-filter.md is excluded by !changes/**
  • changes/312-grammar-ending-annotation-filter.md is excluded by !changes/**
📒 Files selected for processing (7)
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/main.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/token-merger.ts
✅ Files skipped from review due to trivial changes (1)
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/token-merger.ts

Comment thread src/core/services/tokenizer/annotation-stage.test.ts Outdated
@ksyasuda ksyasuda force-pushed the tokenizer-updates branch from 251b6cd to c7996e3 Compare May 2, 2026 22:44

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/runtime/app-lifecycle-actions.test.ts (1)

47-52: ⚡ Quick win

Strengthen this test with order assertions for the new cleanup step.

includes(...) can pass even if the timeout cleanup runs too late. Consider asserting it occurs before window-destroy calls to lock in intended shutdown safety.

Suggested assertion additions
   assert.ok(calls.includes('clear-windows-visible-overlay-poll'));
   assert.ok(calls.includes('clear-linux-mpv-fullscreen-overlay-refresh-timeouts'));
+  assert.ok(
+    calls.indexOf('clear-linux-mpv-fullscreen-overlay-refresh-timeouts') <
+      calls.indexOf('destroy-main-overlay-window'),
+  );
   assert.ok(calls.indexOf('flush-mpv-log') < calls.indexOf('destroy-socket'));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/runtime/app-lifecycle-actions.test.ts` around lines 47 - 52, The
test currently only checks presence of the new timeout-cleanup steps; strengthen
it by asserting their order relative to window-destroy steps: use the existing
calls array and add index comparisons such as
assert.ok(calls.indexOf('clear-windows-visible-overlay-poll') <
calls.indexOf('destroy-tray')) and
assert.ok(calls.indexOf('clear-linux-mpv-fullscreen-overlay-refresh-timeouts') <
calls.indexOf('destroy-tray')) (or compare against whichever window-destroy
symbol is used in the test) so both cleanup actions run before any
destroy-window/destroy-tray teardown steps.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/runtime/app-lifecycle-actions.test.ts`:
- Around line 47-52: The test currently only checks presence of the new
timeout-cleanup steps; strengthen it by asserting their order relative to
window-destroy steps: use the existing calls array and add index comparisons
such as assert.ok(calls.indexOf('clear-windows-visible-overlay-poll') <
calls.indexOf('destroy-tray')) and
assert.ok(calls.indexOf('clear-linux-mpv-fullscreen-overlay-refresh-timeouts') <
calls.indexOf('destroy-tray')) (or compare against whichever window-destroy
symbol is used in the test) so both cleanup actions run before any
destroy-window/destroy-tray teardown steps.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: afd75b99-5954-49d0-891f-d7e25e4ea7e7

📥 Commits

Reviewing files that changed from the base of the PR and between 251b6cd and c7996e3.

⛔ Files ignored due to path filters (17)
  • backlog/tasks/task-304 - Fix-N1-sentence-boundary-counting-across-Yomitan-punctuation-gaps.md is excluded by !backlog/**
  • backlog/tasks/task-305 - Use-Yomitan-word-classes-for-subtitle-token-POS-filtering.md is excluded by !backlog/**
  • backlog/tasks/task-306 - Fix-Hyprland-fullscreen-overlay-geometry-and-hover-pause.md is excluded by !backlog/**
  • backlog/tasks/task-307 - Exclude-kana-only-words-from-N1-subtitle-targets.md is excluded by !backlog/**
  • backlog/tasks/task-308 - Restore-persistent-JLPT-subtitle-underlines.md is excluded by !backlog/**
  • backlog/tasks/task-309 - Accept-modified-follow-up-digits-for-multi-line-sentence-mining.md is excluded by !backlog/**
  • backlog/tasks/task-310 - Suppress-N1-highlight-for-kana-only-candidate-sentences.md is excluded by !backlog/**
  • backlog/tasks/task-311 - Suppress-auxiliary-inflection-fragments-from-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-312 - Suppress-ja-nai-explanatory-ending-subtitle-annotations.md is excluded by !backlog/**
  • changes/305-tokenizer-word-class-pos-filtering.md is excluded by !changes/**
  • changes/306-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/307-kana-nplusone-targets.md is excluded by !changes/**
  • changes/308-jlpt-underlines.md is excluded by !changes/**
  • changes/309-multi-mine-modified-digits.md is excluded by !changes/**
  • changes/311-auxiliary-inflection-annotation-filter.md is excluded by !changes/**
  • changes/312-grammar-ending-annotation-filter.md is excluded by !changes/**
  • docs-site/troubleshooting.md is excluded by !docs-site/**
📒 Files selected for processing (40)
  • package.json
  • plugin/subminer/session_bindings.lua
  • scripts/test-plugin-session-bindings.lua
  • src/core/services/mpv-properties.ts
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/mpv-protocol.ts
  • src/core/services/mpv.test.ts
  • src/core/services/mpv.ts
  • src/core/services/overlay-window.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/main.ts
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/main/runtime/app-lifecycle-actions.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/renderer/handlers/keyboard.test.ts
  • src/renderer/handlers/keyboard.ts
  • src/renderer/handlers/mouse.test.ts
  • src/renderer/handlers/mouse.ts
  • src/renderer/style.css
  • src/renderer/subtitle-render.test.ts
  • src/token-merger.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/window-trackers/hyprland-tracker.ts
✅ Files skipped from review due to trivial changes (14)
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/mpv.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/core/services/mpv-properties.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/renderer/handlers/keyboard.test.ts
  • src/core/services/mpv.ts
  • src/core/services/tokenizer.ts
  • src/main.ts
  • src/token-merger.ts
  • src/core/services/tokenizer.test.ts
🚧 Files skipped from review as they are similar to previous changes (19)
  • src/window-trackers/hyprland-tracker.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • package.json
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/renderer/style.css
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/core/services/mpv-protocol.ts
  • src/core/services/overlay-window.ts
  • scripts/test-plugin-session-bindings.lua
  • src/renderer/handlers/mouse.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/renderer/handlers/keyboard.ts
  • src/window-trackers/hyprland-tracker.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/tokenizer/annotation-stage.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/core/services/tokenizer/parser-selection-stage.ts (1)

27-43: ⚡ Quick win

Centralize grammar-ending lexicon to prevent split/filter drift.

STANDALONE_GRAMMAR_ENDINGS is now a second hardcoded grammar list parallel to src/core/services/tokenizer/subtitle-annotation-filter.ts rules. Divergence here will cause parser-splitting and annotation-filter behavior to silently desync. Consider moving these shared endings into one tokenizer constants module consumed by both stages.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/tokenizer/parser-selection-stage.ts` around lines 27 - 43,
STANDALONE_GRAMMAR_ENDINGS duplicates a grammar lexicon from
subtitle-annotation-filter.ts causing potential drift; extract this set into a
shared tokenizer constants module (e.g., export const STANDALONE_GRAMMAR_ENDINGS
from a new or existing tokenizer/constants.ts), replace the local declaration in
parser-selection-stage.ts with an import of that exported constant, and update
subtitle-annotation-filter.ts to import the same constant so both stages consume
the single source of truth.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/core/services/tokenizer/parser-selection-stage.ts`:
- Around line 27-43: STANDALONE_GRAMMAR_ENDINGS duplicates a grammar lexicon
from subtitle-annotation-filter.ts causing potential drift; extract this set
into a shared tokenizer constants module (e.g., export const
STANDALONE_GRAMMAR_ENDINGS from a new or existing tokenizer/constants.ts),
replace the local declaration in parser-selection-stage.ts with an import of
that exported constant, and update subtitle-annotation-filter.ts to import the
same constant so both stages consume the single source of truth.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 536dd360-9d89-4c99-8d6b-7d811df19d13

📥 Commits

Reviewing files that changed from the base of the PR and between 756b368 and 03c840c.

⛔ Files ignored due to path filters (3)
  • backlog/tasks/task-315 - Suppress-annotations-for-standalone-じゃない-and-です-ending-tokens.md is excluded by !backlog/**
  • backlog/tasks/task-316 - Fix-macOS-launcher-playback-exit-with-background-stats-daemon.md is excluded by !backlog/**
  • changes/316-macos-playback-stats-daemon.md is excluded by !changes/**
📒 Files selected for processing (9)
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/parser-selection-stage.test.ts
  • src/core/services/tokenizer/parser-selection-stage.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/main/runtime/stats-server-routing.test.ts
  • src/main/runtime/stats-server-routing.ts
✅ Files skipped from review due to trivial changes (1)
  • src/core/services/tokenizer/annotation-stage.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/services/tokenizer/subtitle-annotation-filter.ts

@ksyasuda ksyasuda force-pushed the tokenizer-updates branch from 03c840c to 738ae17 Compare May 3, 2026 02:53

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
scripts/test-plugin-session-bindings.lua (1)

128-132: ⚡ Quick win

Assert exact CLI argv length to catch silent argument drift.

You already validate values at indices 1..3; adding an argv length check makes this regression stricter.

Proposed fix
 local call = recorded.async_calls[`#recorded.async_calls`]
 assert_true(call ~= nil, "modified digit should invoke CLI action")
+assert_true(`#call` == 3, "CLI action should pass exactly 3 argv entries")
 assert_true(call[1] == "/tmp/subminer", "CLI action should use configured binary")
 assert_true(call[2] == "--mine-sentence-count", "CLI action should mine sentence count")
 assert_true(call[3] == "3", "CLI action should pass selected count")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/test-plugin-session-bindings.lua` around lines 128 - 132, Add a
strict argv length check after grabbing call from recorded.async_calls: assert
that `#call` equals 3 so silent argument drift is detected; update the test near
the existing assertions that reference call and recorded.async_calls (i.e.,
after local call = recorded.async_calls[`#recorded.async_calls`]) to include an
assertion that the argument vector length is exactly 3 with an explanatory
message.
src/core/services/tokenizer.test.ts (2)

3078-3155: ⚡ Quick win

Broaden the wordClasses mapping matrix a bit.

These cases only exercise prt and aux-v, while the runtime mapping in this PR also covers verb, adjective, and noun-like classes. Adding one representative case for each remaining family would close the main regression gap with very little extra setup.

Possible table-driven shape
+for (const {
+  wordClass,
+  expectedPartOfSpeech,
+  expectedPos1,
+} of [
+  { wordClass: 'v1', expectedPartOfSpeech: PartOfSpeech.verb, expectedPos1: '動詞' },
+  { wordClass: 'adj-i', expectedPartOfSpeech: PartOfSpeech.i_adjective, expectedPos1: '形容詞' },
+  { wordClass: 'n', expectedPartOfSpeech: PartOfSpeech.noun, expectedPos1: '名詞' },
+]) {
+  test(`tokenizeSubtitle maps ${wordClass}`, async () => {
+    // existing makeDepsFromYomitanTokens setup...
+  });
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/tokenizer.test.ts` around lines 3078 - 3155, Tests for
tokenizeSubtitle only cover 'prt' and 'aux-v' Yomitan wordClasses but the
runtime mapping also handles verb, adjective, and noun-like classes; add
additional table-driven test cases using makeDepsFromYomitanTokens to include
one representative token each with wordClasses like 'v' (or 'vb'), 'adj' (or
'adj-i'), and a noun family (e.g., 'n'/'noun') and assert tokenizeSubtitle
returns the expected PartOfSpeech (PartOfSpeech.verb, PartOfSpeech.adjective,
PartOfSpeech.noun or appropriate constants), plus correct pos1/pos2 when
available; place these alongside the existing tests referencing tokenizeSubtitle
and PartOfSpeech so the mapping coverage is exercised without changing runtime
code.

4246-4449: 🏗️ Heavy lift

Consider moving these filter-centric cases into a focused test file.

This batch is validating subtitle-annotation filtering more than top-level tokenization, and src/core/services/tokenizer.test.ts is already getting hard to scan. Moving these scenarios into src/core/services/tokenizer/annotation-stage.test.ts or a dedicated filter test would keep the integration suite tighter.

As per coding guidelines, "TypeScript files should be kept small and follow existing patterns."

Also applies to: 4670-4914

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/tokenizer.test.ts` around lines 4246 - 4449, These two
filter-focused tests (the ones named "tokenizeSubtitle clears annotations for
ja-nai explanatory endings while preserving lexical content" and
"tokenizeSubtitle clears annotations for standalone polite copula endings
without POS metadata") should be moved out of the large tokenizer.test.ts and
into a new focused test file named annotation-stage.test.ts; copy the tests
verbatim but update their imports to reference the same helpers
(tokenizeSubtitle, makeDepsFromYomitanTokens, PartOfSpeech) from the module
exports used in the original file, ensure any test helpers remain
exported/imported so the tests compile, and run the suite to confirm the new
test file is detected by the runner.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@scripts/test-plugin-session-bindings.lua`:
- Line 13: Replace the hardcoded os.execute("mkdir -p .tmp") call with a
shell-agnostic creation: detect the platform via package.config:sub(1,1) and
call os.execute("mkdir .tmp") on Windows vs os.execute("mkdir -p .tmp") on Unix,
or alternatively use a Lua filesystem API (e.g., lfs.mkdir) if available; update
the os.execute invocation accordingly so the test creates the .tmp directory on
both Windows and Unix runners.

In `@src/core/services/tokenizer.ts`:
- Around line 99-100: The early-return branches call
stripSubtitleAnnotationMetadata(tokens) without forwarding the
TokenizerAnnotationOptions (including the new sourceText), so
sentence-boundary-aware filtering never sees sourceText; update each call site
(the early-return path and the other occurrences around the tokenization logic)
to forward the same TokenizerAnnotationOptions object (or at minimum {
sourceText }) into stripSubtitleAnnotationMetadata(tokens, options), and if
necessary update stripSubtitleAnnotationMetadata's signature to accept and use
TokenizerAnnotationOptions so sourceText is honored during metadata stripping.

In `@src/core/services/tokenizer/parser-selection-stage.ts`:
- Around line 27-43: STANDALONE_GRAMMAR_ENDINGS only covers the "です"/"じゃない"
family but must match the broader copula/explanatory forms used by
subtitle-annotation-filter; update the STANDALONE_GRAMMAR_ENDINGS constant to
include other copula/explanatory endings (e.g., だ, だった, では, ではない, でしょう, だろう, だよ,
だね, なんだ, etc.) so splitting behavior mirrors the subtitle exclusion rules, and
make the same extended change to the duplicate list found around the 162-168
range to keep both definitions consistent (use the existing constant name
STANDALONE_GRAMMAR_ENDINGS to locate and update both occurrences).

---

Nitpick comments:
In `@scripts/test-plugin-session-bindings.lua`:
- Around line 128-132: Add a strict argv length check after grabbing call from
recorded.async_calls: assert that `#call` equals 3 so silent argument drift is
detected; update the test near the existing assertions that reference call and
recorded.async_calls (i.e., after local call =
recorded.async_calls[`#recorded.async_calls`]) to include an assertion that the
argument vector length is exactly 3 with an explanatory message.

In `@src/core/services/tokenizer.test.ts`:
- Around line 3078-3155: Tests for tokenizeSubtitle only cover 'prt' and 'aux-v'
Yomitan wordClasses but the runtime mapping also handles verb, adjective, and
noun-like classes; add additional table-driven test cases using
makeDepsFromYomitanTokens to include one representative token each with
wordClasses like 'v' (or 'vb'), 'adj' (or 'adj-i'), and a noun family (e.g.,
'n'/'noun') and assert tokenizeSubtitle returns the expected PartOfSpeech
(PartOfSpeech.verb, PartOfSpeech.adjective, PartOfSpeech.noun or appropriate
constants), plus correct pos1/pos2 when available; place these alongside the
existing tests referencing tokenizeSubtitle and PartOfSpeech so the mapping
coverage is exercised without changing runtime code.
- Around line 4246-4449: These two filter-focused tests (the ones named
"tokenizeSubtitle clears annotations for ja-nai explanatory endings while
preserving lexical content" and "tokenizeSubtitle clears annotations for
standalone polite copula endings without POS metadata") should be moved out of
the large tokenizer.test.ts and into a new focused test file named
annotation-stage.test.ts; copy the tests verbatim but update their imports to
reference the same helpers (tokenizeSubtitle, makeDepsFromYomitanTokens,
PartOfSpeech) from the module exports used in the original file, ensure any test
helpers remain exported/imported so the tests compile, and run the suite to
confirm the new test file is detected by the runner.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ee2588e5-c33c-491e-854f-98eeb9b483fc

📥 Commits

Reviewing files that changed from the base of the PR and between 03c840c and 738ae17.

⛔ Files ignored due to path filters (20)
  • backlog/tasks/task-304 - Fix-N1-sentence-boundary-counting-across-Yomitan-punctuation-gaps.md is excluded by !backlog/**
  • backlog/tasks/task-305 - Use-Yomitan-word-classes-for-subtitle-token-POS-filtering.md is excluded by !backlog/**
  • backlog/tasks/task-306 - Fix-Hyprland-fullscreen-overlay-geometry-and-hover-pause.md is excluded by !backlog/**
  • backlog/tasks/task-307 - Exclude-kana-only-words-from-N1-subtitle-targets.md is excluded by !backlog/**
  • backlog/tasks/task-308 - Restore-persistent-JLPT-subtitle-underlines.md is excluded by !backlog/**
  • backlog/tasks/task-309 - Accept-modified-follow-up-digits-for-multi-line-sentence-mining.md is excluded by !backlog/**
  • backlog/tasks/task-310 - Suppress-N1-highlight-for-kana-only-candidate-sentences.md is excluded by !backlog/**
  • backlog/tasks/task-311 - Suppress-auxiliary-inflection-fragments-from-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-312 - Suppress-ja-nai-explanatory-ending-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-315 - Suppress-annotations-for-standalone-じゃない-and-です-ending-tokens.md is excluded by !backlog/**
  • backlog/tasks/task-316 - Fix-macOS-launcher-playback-exit-with-background-stats-daemon.md is excluded by !backlog/**
  • changes/305-tokenizer-word-class-pos-filtering.md is excluded by !changes/**
  • changes/306-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/307-kana-nplusone-targets.md is excluded by !changes/**
  • changes/308-jlpt-underlines.md is excluded by !changes/**
  • changes/309-multi-mine-modified-digits.md is excluded by !changes/**
  • changes/311-auxiliary-inflection-annotation-filter.md is excluded by !changes/**
  • changes/312-grammar-ending-annotation-filter.md is excluded by !changes/**
  • changes/316-macos-playback-stats-daemon.md is excluded by !changes/**
  • docs-site/troubleshooting.md is excluded by !docs-site/**
📒 Files selected for processing (44)
  • package.json
  • plugin/subminer/session_bindings.lua
  • scripts/test-plugin-session-bindings.lua
  • src/core/services/mpv-properties.ts
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/mpv-protocol.ts
  • src/core/services/mpv.test.ts
  • src/core/services/mpv.ts
  • src/core/services/overlay-window.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/tokenizer/parser-selection-stage.test.ts
  • src/core/services/tokenizer/parser-selection-stage.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/main.ts
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/main/runtime/app-lifecycle-actions.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/main/runtime/stats-server-routing.test.ts
  • src/main/runtime/stats-server-routing.ts
  • src/renderer/handlers/keyboard.test.ts
  • src/renderer/handlers/keyboard.ts
  • src/renderer/handlers/mouse.test.ts
  • src/renderer/handlers/mouse.ts
  • src/renderer/style.css
  • src/renderer/subtitle-render.test.ts
  • src/token-merger.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/window-trackers/hyprland-tracker.ts
✅ Files skipped from review due to trivial changes (21)
  • src/core/services/mpv-properties.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/core/services/mpv-protocol.test.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/renderer/handlers/keyboard.test.ts
  • src/renderer/style.css
  • src/core/services/overlay-window.ts
  • src/main/runtime/stats-server-routing.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/renderer/handlers/keyboard.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • plugin/subminer/session_bindings.lua
  • src/core/services/mpv.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/core/services/mpv.test.ts
  • src/renderer/handlers/mouse.test.ts
🚧 Files skipped from review as they are similar to previous changes (14)
  • src/core/services/mpv-protocol.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/main/runtime/stats-server-routing.ts
  • package.json
  • src/main/runtime/app-lifecycle-actions.ts
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/window-trackers/hyprland-tracker.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/token-merger.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/tokenizer/annotation-stage.test.ts

Comment thread scripts/test-plugin-session-bindings.lua Outdated
Comment thread src/core/services/tokenizer.ts
Comment thread src/core/services/tokenizer/parser-selection-stage.ts Outdated
@ksyasuda ksyasuda force-pushed the tokenizer-updates branch from 738ae17 to 910c240 Compare May 3, 2026 03:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/runtime/mpv-main-event-main-deps.ts`:
- Around line 85-93: The two checks are inconsistent:
hasInitialPlaybackQuitOnDisconnectArg() includes managedPlayback, jellyfinPlay,
and youtubePlay, but shouldQuitOnDisconnectWhenOverlayRuntimeInitialized() only
checks managedPlayback and youtubePlay; update
shouldQuitOnDisconnectWhenOverlayRuntimeInitialized (in
mpv-main-event-main-deps.ts) to use the same predicate as
hasInitialPlaybackQuitOnDisconnectArg by checking
deps.appState.initialArgs?.managedPlayback ||
deps.appState.initialArgs?.jellyfinPlay ||
deps.appState.initialArgs?.youtubePlay so the overlay-initialized path arms
quit-on-disconnect the same way.

In `@src/renderer/handlers/mouse.test.ts`:
- Around line 1346-1354: The test currently stubs document.elementFromPoint to
always return ctx.dom.subtitleContainer so it doesn't actually validate that
handlePrimaryMouseEnter uses event.clientX/Y; change the stub to inspect its (x,
y) args and return ctx.dom.subtitleContainer only when those coordinates fall
inside the subtitle container's bounds (use
ctx.dom.subtitleContainer.getBoundingClientRect() or an explicit rect on
ctx.dom), otherwise return null; update the similar stub at the other location
mentioned (around the block at 1373-1379) so both tests perform real hit-testing
based on event coordinates.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ced4bfa8-fa8b-42c1-b28e-cc1f31ca4b27

📥 Commits

Reviewing files that changed from the base of the PR and between 738ae17 and 910c240.

⛔ Files ignored due to path filters (26)
  • backlog/tasks/task-304 - Fix-N1-sentence-boundary-counting-across-Yomitan-punctuation-gaps.md is excluded by !backlog/**
  • backlog/tasks/task-305 - Use-Yomitan-word-classes-for-subtitle-token-POS-filtering.md is excluded by !backlog/**
  • backlog/tasks/task-306 - Fix-Hyprland-fullscreen-overlay-geometry-and-hover-pause.md is excluded by !backlog/**
  • backlog/tasks/task-307 - Exclude-kana-only-words-from-N1-subtitle-targets.md is excluded by !backlog/**
  • backlog/tasks/task-308 - Restore-persistent-JLPT-subtitle-underlines.md is excluded by !backlog/**
  • backlog/tasks/task-309 - Accept-modified-follow-up-digits-for-multi-line-sentence-mining.md is excluded by !backlog/**
  • backlog/tasks/task-310 - Suppress-N1-highlight-for-kana-only-candidate-sentences.md is excluded by !backlog/**
  • backlog/tasks/task-311 - Suppress-auxiliary-inflection-fragments-from-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-312 - Suppress-ja-nai-explanatory-ending-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-315 - Suppress-annotations-for-standalone-じゃない-and-です-ending-tokens.md is excluded by !backlog/**
  • backlog/tasks/task-316 - Fix-macOS-launcher-playback-exit-with-background-stats-daemon.md is excluded by !backlog/**
  • backlog/tasks/task-318 - Keep-JLPT-underline-color-fixed-after-lookup-selection.md is excluded by !backlog/**
  • backlog/tasks/task-319 - Suppress-annotations-for-expressive-interjection-subtitles.md is excluded by !backlog/**
  • backlog/tasks/task-320 - Refresh-current-subtitle-known-word-highlight-after-successful-mining.md is excluded by !backlog/**
  • changes/305-tokenizer-word-class-pos-filtering.md is excluded by !changes/**
  • changes/306-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/307-kana-nplusone-targets.md is excluded by !changes/**
  • changes/308-jlpt-underlines.md is excluded by !changes/**
  • changes/309-multi-mine-modified-digits.md is excluded by !changes/**
  • changes/311-auxiliary-inflection-annotation-filter.md is excluded by !changes/**
  • changes/312-grammar-ending-annotation-filter.md is excluded by !changes/**
  • changes/316-macos-playback-stats-daemon.md is excluded by !changes/**
  • changes/318-jlpt-underline-selection.md is excluded by !changes/**
  • changes/319-interjection-annotation-filter.md is excluded by !changes/**
  • changes/320-current-subtitle-known-highlight.md is excluded by !changes/**
  • docs-site/troubleshooting.md is excluded by !docs-site/**
📒 Files selected for processing (47)
  • package.json
  • plugin/subminer/session_bindings.lua
  • scripts/test-plugin-session-bindings.lua
  • src/anki-integration.test.ts
  • src/anki-integration.ts
  • src/anki-integration/known-word-cache.ts
  • src/core/services/mpv-properties.ts
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/mpv-protocol.ts
  • src/core/services/mpv.test.ts
  • src/core/services/mpv.ts
  • src/core/services/overlay-window.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/tokenizer/parser-selection-stage.test.ts
  • src/core/services/tokenizer/parser-selection-stage.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/main.ts
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/main/runtime/app-lifecycle-actions.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/main/runtime/stats-server-routing.test.ts
  • src/main/runtime/stats-server-routing.ts
  • src/renderer/handlers/keyboard.test.ts
  • src/renderer/handlers/keyboard.ts
  • src/renderer/handlers/mouse.test.ts
  • src/renderer/handlers/mouse.ts
  • src/renderer/style.css
  • src/renderer/subtitle-render.test.ts
  • src/token-merger.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/window-trackers/hyprland-tracker.ts
✅ Files skipped from review due to trivial changes (11)
  • src/core/services/overlay-window.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/renderer/handlers/keyboard.ts
  • src/renderer/handlers/keyboard.test.ts
  • package.json
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/renderer/style.css
  • scripts/test-plugin-session-bindings.lua
  • src/main/runtime/stats-server-routing.test.ts
  • src/core/services/mpv.test.ts
  • src/core/services/mpv-protocol.ts
🚧 Files skipped from review as they are similar to previous changes (19)
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/renderer/subtitle-render.test.ts
  • src/core/services/mpv.ts
  • plugin/subminer/session_bindings.lua
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/main/runtime/app-lifecycle-actions.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/renderer/handlers/mouse.ts
  • src/window-trackers/hyprland-tracker.ts
  • src/main/runtime/stats-server-routing.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts

Comment thread src/main/runtime/mpv-main-event-main-deps.ts Outdated
Comment thread src/renderer/handlers/mouse.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/core/services/tokenizer/parser-selection-stage.ts (1)

145-150: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don't require headwords before splitting standalone endings.

The later split path already falls back to segmentText when no headword is present, so this headword.length > 0 gate makes text-only です / じゃない segments stay merged into the previous token even though YomitanParseSegment.headwords is optional.

Suggested fix
 function isStandaloneGrammarEndingSegment(segment: YomitanParseSegment): boolean {
   const surface = segment.text?.trim() ?? '';
   const headword = extractYomitanHeadword(segment).trim();
-  return (
-    headword.length > 0 &&
-    (isStandaloneGrammarEndingText(surface) || isStandaloneGrammarEndingText(headword))
-  );
+  return isStandaloneGrammarEndingText(surface) || isStandaloneGrammarEndingText(headword);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/tokenizer/parser-selection-stage.ts` around lines 145 -
150, The guard requiring a non-empty headword in
isStandaloneGrammarEndingSegment prevents text-only endings (e.g. "です", "じゃない")
from being recognized; remove the headword.length > 0 check so the function
returns true when either the trimmed segment.text (surface) or the extracted
headword matches via isStandaloneGrammarEndingText, using
extractYomitanHeadword(segment) and segment.text as-is to allow the existing
fallback path to handle missing headwords.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/anki-integration/card-creation.ts`:
- Around line 251-258: The manual-update path is still overwriting expression
audio because sentenceAudioField may be the fallback config.fields.audio even
when the note lacks a sentence-audio field; change the logic so you only write
updatedFields[sentenceAudioField] when that field actually exists on the note.
After computing sentenceAudioField (from getResolvedSentenceAudioFieldName())
add a guard that checks noteInfo.fields.hasOwnProperty(sentenceAudioField) (or
similar existence check) and skip the mergeFieldValue/update if the field is not
present; keep using deps.mergeFieldValue for the real sentence-audio field. Add
a regression test that runs the manual clipboard update on a note missing
SentenceAudio to ensure no ExpressionAudio is overwritten.

In `@src/core/services/tokenizer/subtitle-annotation-filter.ts`:
- Around line 17-20: Remove the literal phrases 'たって' and 'だって' from the broad
blacklist (SUBTITLE_ANNOTATION_EXCLUDED_TERMS) so they don't suppress tokens
whose reading normalizes to those strings; instead rely only on the dedicated
STANDALONE_GRAMMAR_PARTICLE_PHRASES / STANDALONE_GRAMMAR_PARTICLE_PHRASES_SET
and the isStandaloneGrammarParticle() helper to filter real standalone-particle
cases, and keep isExcludedByTerm() behavior unchanged (it should continue to
check both term and reading but no longer match these two phrases via the broad
blacklist).

---

Duplicate comments:
In `@src/core/services/tokenizer/parser-selection-stage.ts`:
- Around line 145-150: The guard requiring a non-empty headword in
isStandaloneGrammarEndingSegment prevents text-only endings (e.g. "です", "じゃない")
from being recognized; remove the headword.length > 0 check so the function
returns true when either the trimmed segment.text (surface) or the extracted
headword matches via isStandaloneGrammarEndingText, using
extractYomitanHeadword(segment) and segment.text as-is to allow the existing
fallback path to handle missing headwords.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d8ed1cb0-6c1f-4a43-a724-cef7d9057eeb

📥 Commits

Reviewing files that changed from the base of the PR and between 910c240 and 59f8de5.

⛔ Files ignored due to path filters (7)
  • backlog/tasks/task-315 - Suppress-annotations-for-standalone-じゃない-and-です-ending-tokens.md is excluded by !backlog/**
  • backlog/tasks/task-321 - Preserve-word-audio-during-manual-clipboard-card-updates.md is excluded by !backlog/**
  • changes/321-grammar-ending-pattern-filter.md is excluded by !changes/**
  • changes/322-preserve-word-audio-manual-update.md is excluded by !changes/**
  • docs-site/anki-integration.md is excluded by !docs-site/**
  • docs-site/configuration.md is excluded by !docs-site/**
  • docs-site/mining-workflow.md is excluded by !docs-site/**
📒 Files selected for processing (8)
  • src/anki-integration/card-creation-manual-update.test.ts
  • src/anki-integration/card-creation.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/grammar-ending.ts
  • src/core/services/tokenizer/parser-selection-stage.test.ts
  • src/core/services/tokenizer/parser-selection-stage.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/services/tokenizer/annotation-stage.ts

Comment thread src/anki-integration/card-creation.ts
Comment thread src/core/services/tokenizer/subtitle-annotation-filter.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/runtime/mpv-main-event-main-deps.ts (1)

85-97: ⚡ Quick win

Deduplicate quit-on-disconnect predicate to prevent drift.

Both methods now match (great), but the duplicated condition is easy to desync again. Please extract a shared local predicate and reuse it.

♻️ Proposed refactor
+  const hasInitialPlaybackQuitOnDisconnectArg = (): boolean =>
+    Boolean(
+      deps.appState.initialArgs?.managedPlayback ||
+        deps.appState.initialArgs?.jellyfinPlay ||
+        deps.appState.initialArgs?.youtubePlay,
+    );
+
   return () => ({
@@
-    hasInitialPlaybackQuitOnDisconnectArg: () =>
-      Boolean(
-        deps.appState.initialArgs?.managedPlayback ||
-        deps.appState.initialArgs?.jellyfinPlay ||
-        deps.appState.initialArgs?.youtubePlay,
-      ),
+    hasInitialPlaybackQuitOnDisconnectArg,
@@
-    shouldQuitOnDisconnectWhenOverlayRuntimeInitialized: () =>
-      Boolean(
-        deps.appState.initialArgs?.managedPlayback ||
-        deps.appState.initialArgs?.jellyfinPlay ||
-        deps.appState.initialArgs?.youtubePlay,
-      ),
+    shouldQuitOnDisconnectWhenOverlayRuntimeInitialized:
+      hasInitialPlaybackQuitOnDisconnectArg,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/runtime/mpv-main-event-main-deps.ts` around lines 85 - 97, The two
duplicated predicates in hasInitialPlaybackQuitOnDisconnectArg and
shouldQuitOnDisconnectWhenOverlayRuntimeInitialized should be extracted into a
shared predicate and reused to avoid drift: create a local helper (e.g., const
initialPlaybackQuitOnDisconnect = () =>
Boolean(deps.appState.initialArgs?.managedPlayback ||
deps.appState.initialArgs?.jellyfinPlay ||
deps.appState.initialArgs?.youtubePlay) or a simple boolean constant computed
once) and replace the existing inline Boolean(...) expressions in both
hasInitialPlaybackQuitOnDisconnectArg and
shouldQuitOnDisconnectWhenOverlayRuntimeInitialized to call/return that shared
symbol; ensure isOverlayRuntimeInitialized remains unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/main/runtime/mpv-main-event-main-deps.ts`:
- Around line 85-97: The two duplicated predicates in
hasInitialPlaybackQuitOnDisconnectArg and
shouldQuitOnDisconnectWhenOverlayRuntimeInitialized should be extracted into a
shared predicate and reused to avoid drift: create a local helper (e.g., const
initialPlaybackQuitOnDisconnect = () =>
Boolean(deps.appState.initialArgs?.managedPlayback ||
deps.appState.initialArgs?.jellyfinPlay ||
deps.appState.initialArgs?.youtubePlay) or a simple boolean constant computed
once) and replace the existing inline Boolean(...) expressions in both
hasInitialPlaybackQuitOnDisconnectArg and
shouldQuitOnDisconnectWhenOverlayRuntimeInitialized to call/return that shared
symbol; ensure isOverlayRuntimeInitialized remains unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 09f6fde7-e263-4f27-a0e0-3065ef275458

📥 Commits

Reviewing files that changed from the base of the PR and between 59f8de5 and a8e1112.

⛔ Files ignored due to path filters (2)
  • backlog/tasks/task-322 - Fix-failing-CI-checks-on-PR-57.md is excluded by !backlog/**
  • changes/319-interjection-annotation-filter.md is excluded by !changes/**
📒 Files selected for processing (7)
  • scripts/test-plugin-session-bindings.lua
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/renderer/handlers/mouse.test.ts
  • src/renderer/subtitle-render.test.ts
✅ Files skipped from review due to trivial changes (1)
  • scripts/test-plugin-session-bindings.lua
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer.ts
  • src/renderer/handlers/mouse.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugin/subminer/process.lua (1)

298-303: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve the startup gate for duplicate auto-starts while readiness is still pending.

Line 302 unconditionally disarms the gate. If file-loaded fires again before subminer-autoplay-ready, this path resumes playback early and bypasses the intended pause-until-ready behavior.

💡 Suggested fix
 		if state.overlay_running then
 			if overrides.auto_start_trigger == true then
 				subminer_log("debug", "process", "Auto-start ignored because overlay is already running")
 				local socket_path = overrides.socket_path or opts.socket_path
-				disarm_auto_play_ready_gate()
+				if not state.auto_play_ready_gate_armed then
+					disarm_auto_play_ready_gate()
+				end
 				local visibility_action = resolve_visible_overlay_startup()
 						and "show-visible-overlay"
 					or "hide-visible-overlay"
 				run_control_command_async(visibility_action, {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugin/subminer/process.lua` around lines 298 - 303, In the branch where
state.overlay_running and overrides.auto_start_trigger are true, do not
unconditionally call disarm_auto_play_ready_gate(); instead first call
resolve_visible_overlay_startup() and inspect its return (e.g.,
visibility_action) and only call disarm_auto_play_ready_gate() when the action
indicates the overlay should start immediately (not when it indicates
defer/wait), so duplicate file-loaded events won't prematurely release the
auto-play gate; update the sequence around state.overlay_running,
overrides.auto_start_trigger, disarm_auto_play_ready_gate(), and
resolve_visible_overlay_startup() accordingly.
🧹 Nitpick comments (2)
src/main/runtime/mpv-main-event-main-deps.ts (1)

85-97: ⚡ Quick win

Deduplicate quit-on-disconnect predicate to prevent future drift.

Both exported checks now match, but they duplicate the same condition and can diverge again during future edits.

♻️ Proposed refactor
+  const hasInitialPlaybackQuitOnDisconnectArg = (): boolean =>
+    Boolean(
+      deps.appState.initialArgs?.managedPlayback ||
+        deps.appState.initialArgs?.jellyfinPlay ||
+        deps.appState.initialArgs?.youtubePlay,
+    );
+
   return () => ({
@@
-    hasInitialPlaybackQuitOnDisconnectArg: () =>
-      Boolean(
-        deps.appState.initialArgs?.managedPlayback ||
-        deps.appState.initialArgs?.jellyfinPlay ||
-        deps.appState.initialArgs?.youtubePlay,
-      ),
+    hasInitialPlaybackQuitOnDisconnectArg,
@@
-    shouldQuitOnDisconnectWhenOverlayRuntimeInitialized: () =>
-      Boolean(
-        deps.appState.initialArgs?.managedPlayback ||
-        deps.appState.initialArgs?.jellyfinPlay ||
-        deps.appState.initialArgs?.youtubePlay,
-      ),
+    shouldQuitOnDisconnectWhenOverlayRuntimeInitialized: hasInitialPlaybackQuitOnDisconnectArg,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/runtime/mpv-main-event-main-deps.ts` around lines 85 - 97, Extract
the duplicated quit-on-disconnect check into a single shared predicate and have
both hasInitialPlaybackQuitOnDisconnectArg and
shouldQuitOnDisconnectWhenOverlayRuntimeInitialized call it; specifically,
create a helper (e.g., initialPlaybackQuitOnDisconnect or
getInitialPlaybackQuitOnDisconnect) that returns
Boolean(deps.appState.initialArgs?.managedPlayback ||
deps.appState.initialArgs?.jellyfinPlay ||
deps.appState.initialArgs?.youtubePlay) and replace the duplicated inline logic
in both exported checks to call that helper so the condition is defined in one
place and cannot drift.
scripts/test-plugin-start-gate.lua (1)

898-903: ⚡ Quick win

Add a duplicate file-loaded-before-ready regression case.

This scenario currently calls the second file-loaded only after the first ready signal. Add a variant where the second file-loaded fires before subminer-autoplay-ready to protect pause-until-ready semantics from premature gate disarm regressions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/test-plugin-start-gate.lua` around lines 898 - 903, Add a regression
variant that fires the second file-loaded before the ready signal to ensure
pause-until-ready semantics hold: duplicate the existing sequence around
fire_event(recorded, "file-loaded") and
recorded.script_messages["subminer-autoplay-ready"]() so that you call
fire_event(recorded, "file-loaded") a second time prior to invoking
recorded.script_messages["subminer-autoplay-ready"](), then invoke the ready
handler and repeat the same assertions; locate the block using fire_event,
recorded, and the "subminer-autoplay-ready" key to insert the new pre-ready
file-loaded case and reuse the same assert_true checks to validate behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@plugin/subminer/process.lua`:
- Around line 298-303: In the branch where state.overlay_running and
overrides.auto_start_trigger are true, do not unconditionally call
disarm_auto_play_ready_gate(); instead first call
resolve_visible_overlay_startup() and inspect its return (e.g.,
visibility_action) and only call disarm_auto_play_ready_gate() when the action
indicates the overlay should start immediately (not when it indicates
defer/wait), so duplicate file-loaded events won't prematurely release the
auto-play gate; update the sequence around state.overlay_running,
overrides.auto_start_trigger, disarm_auto_play_ready_gate(), and
resolve_visible_overlay_startup() accordingly.

---

Nitpick comments:
In `@scripts/test-plugin-start-gate.lua`:
- Around line 898-903: Add a regression variant that fires the second
file-loaded before the ready signal to ensure pause-until-ready semantics hold:
duplicate the existing sequence around fire_event(recorded, "file-loaded") and
recorded.script_messages["subminer-autoplay-ready"]() so that you call
fire_event(recorded, "file-loaded") a second time prior to invoking
recorded.script_messages["subminer-autoplay-ready"](), then invoke the ready
handler and repeat the same assertions; locate the block using fire_event,
recorded, and the "subminer-autoplay-ready" key to insert the new pre-ready
file-loaded case and reuse the same assert_true checks to validate behavior.

In `@src/main/runtime/mpv-main-event-main-deps.ts`:
- Around line 85-97: Extract the duplicated quit-on-disconnect check into a
single shared predicate and have both hasInitialPlaybackQuitOnDisconnectArg and
shouldQuitOnDisconnectWhenOverlayRuntimeInitialized call it; specifically,
create a helper (e.g., initialPlaybackQuitOnDisconnect or
getInitialPlaybackQuitOnDisconnect) that returns
Boolean(deps.appState.initialArgs?.managedPlayback ||
deps.appState.initialArgs?.jellyfinPlay ||
deps.appState.initialArgs?.youtubePlay) and replace the duplicated inline logic
in both exported checks to call that helper so the condition is defined in one
place and cannot drift.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 92879433-e70b-44bb-a315-51df020b22e7

📥 Commits

Reviewing files that changed from the base of the PR and between 59f8de5 and b3831b3.

⛔ Files ignored due to path filters (6)
  • backlog/tasks/task-322 - Fix-failing-CI-checks-on-PR-57.md is excluded by !backlog/**
  • backlog/tasks/task-323 - Fix-macOS-overlay-hiding-while-mpv-remains-active.md is excluded by !backlog/**
  • backlog/tasks/task-324 - Fix-mpv-playlist-changes-re-running-app-warmups.md is excluded by !backlog/**
  • changes/319-interjection-annotation-filter.md is excluded by !changes/**
  • changes/323-macos-overlay-tracker-flaps.md is excluded by !changes/**
  • changes/324-mpv-playlist-overlay-reuse.md is excluded by !changes/**
📒 Files selected for processing (11)
  • plugin/subminer/process.lua
  • scripts/test-plugin-session-bindings.lua
  • scripts/test-plugin-start-gate.lua
  • src/core/services/overlay-visibility.test.ts
  • src/core/services/overlay-visibility.ts
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/renderer/handlers/mouse.test.ts
  • src/renderer/subtitle-render.test.ts
✅ Files skipped from review due to trivial changes (2)
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/renderer/handlers/mouse.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugin/subminer/process.lua (1)

298-303: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t clear an active startup gate on duplicate auto-start.

When a duplicate file-loaded arrives before readiness, Line 302 disarms the currently armed gate and resumes playback early. That bypasses auto_start_pause_until_ready for the active startup cycle.

Proposed fix
 		if state.overlay_running then
 			if overrides.auto_start_trigger == true then
 				subminer_log("debug", "process", "Auto-start ignored because overlay is already running")
 				local socket_path = overrides.socket_path or opts.socket_path
-				disarm_auto_play_ready_gate()
+				-- Preserve an active startup gate; only clear stale/inactive state.
+				if not state.auto_play_ready_gate_armed then
+					disarm_auto_play_ready_gate()
+				end
 				local visibility_action = resolve_visible_overlay_startup()
 						and "show-visible-overlay"
 					or "hide-visible-overlay"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugin/subminer/process.lua` around lines 298 - 303, The current branch
disarms the startup gate unconditionally when a duplicate auto-start arrives
while state.overlay_running, which lets playback resume early; change the logic
so disarm_auto_play_ready_gate() is not called for duplicate triggers when
state.overlay_running is true. Specifically, in the block handling
overrides.auto_start_trigger and state.overlay_running, remove or skip the call
to disarm_auto_play_ready_gate() (keep the log and socket_path resolution), and
only call disarm_auto_play_ready_gate() from the path that actually starts a new
overlay (the branch where state.overlay_running is false or where you initiate
startup), ensuring auto_start_pause_until_ready remains enforced for the active
startup cycle.
🧹 Nitpick comments (3)
src/renderer/subtitle-render.test.ts (1)

910-912: ⚡ Quick win

Harden the “no underline” guard to catch shorthand text-decoration.

Current check blocks text-decoration-line: underline but still allows text-decoration: underline .... Add one more negative assertion so this regression can’t slip through.

Proposed test hardening
     assert.doesNotMatch(plainJlptBlock, /(?:^|\n)\s*color\s*:/m);
     assert.doesNotMatch(plainJlptBlock, /text-decoration-line:\s*underline;/);
+    assert.doesNotMatch(plainJlptBlock, /text-decoration\s*:[^;]*\bunderline\b/i);
     assert.match(
       plainJlptBlock,
       new RegExp(`border-bottom:\\s*2px\\s+solid\\s+var\\(--subtitle-jlpt-n${level}-color,`),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/subtitle-render.test.ts` around lines 910 - 912, The test
currently ensures no 'text-decoration-line: underline' but misses shorthand
'text-decoration: underline'; update the assertions around plainJlptBlock (used
in subtitle-render.test) to also assert.doesNotMatch(plainJlptBlock,
/(?:^|\n)\s*text-decoration\s*:/) or a regex specifically blocking
'text-decoration:\s*underline' so any shorthand underline usage is rejected;
modify the block where assert.doesNotMatch(plainJlptBlock,
/text-decoration-line:\s*underline;/) appears to include this additional
negative assertion referencing plainJlptBlock.
src/main/runtime/mpv-main-event-actions.test.ts (1)

196-224: ⚡ Quick win

Add a rejection-path test for the new post-watch error handler.

This test only validates the success path. Please also assert that a rejected maybeRunAnilistPostWatchUpdate calls logError and does not break subsequent handler behavior.

Proposed test addition
+test('time-pos handler logs when post-watch update rejects', async () => {
+  const calls: string[] = [];
+  const timeHandler = createHandleMpvTimePosChangeHandler({
+    recordPlaybackPosition: (time) => calls.push(`time:${time}`),
+    reportJellyfinRemoteProgress: (force) => calls.push(`progress:${force ? 'force' : 'normal'}`),
+    refreshDiscordPresence: () => calls.push('presence'),
+    maybeRunAnilistPostWatchUpdate: async () => {
+      throw new Error('boom');
+    },
+    logError: () => calls.push('post-watch-error'),
+  });
+
+  timeHandler({ time: 12.5 });
+  await Promise.resolve(); // allow catch microtask to run
+
+  assert.deepEqual(calls, ['time:12.5', 'progress:normal', 'presence', 'post-watch-error']);
+});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/runtime/mpv-main-event-actions.test.ts` around lines 196 - 224, The
test currently only covers the success path for
createHandleMpvTimePosChangeHandler's maybeRunAnilistPostWatchUpdate; add a
counterpart that simulates maybeRunAnilistPostWatchUpdate rejecting and assert
that the supplied logError is called and the rest of the handlers still run in
order. Specifically, create a test that supplies a
maybeRunAnilistPostWatchUpdate which returns a rejected promise, a logError spy
that records calls, then invoke timeHandler({ time: ... }) followed by
pauseHandler({ paused: ... }) and assert the calls array includes the
'post-watch-error' entry (or that logError was called) in place of the success
'post-watch' and that subsequent progress/presence/pause handlers still execute
as before; use the same helper factories createHandleMpvTimePosChangeHandler and
createHandleMpvPauseChangeHandler to locate the code under test.
scripts/test-plugin-start-gate.lua (1)

909-925: ⚡ Quick win

Add a pre-ready duplicate case to lock gate semantics.

This scenario only tests a duplicate file-loaded after the first ready signal. Add a sibling case where the second file-loaded happens before subminer-autoplay-ready, then assert pause remains armed until the first ready event.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/test-plugin-start-gate.lua` around lines 909 - 925, Add a sibling
test case in scripts/test-plugin-start-gate.lua that simulates a second
"file-loaded" firing before the first "subminer-autoplay-ready" to verify that
the pause-until-ready gate stays armed until the ready event; locate the
existing duplicate case near the assertions using helpers like
count_osd_message(recorded.osd, ...), count_property_set(recorded.property_sets,
"pause", ...), and the event names "file-loaded" and "subminer-autoplay-ready",
then create a new test sequence that triggers: initial file-loaded -> pause
should be set true, trigger a duplicate file-loaded (pre-ready) -> assert pause
remains true and no extra ready/tokenization messages are emitted yet, then emit
subminer-autoplay-ready -> assert tokenization ready message appears once and
pause is cleared once (count_property_set "pause", false == 1).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/services/anilist/anilist-updater.ts`:
- Around line 239-245: The current return unconditionally substitutes
parseMediaInfo(target) values for season/episode when guessit (season/episode)
are undefined; instead, gate use of fallback.season and fallback.episode by the
parser confidence—only apply fallback values when parseMediaInfo(target)
indicates high confidence (e.g., confidence >= a safe threshold or a
highConfidence flag). Update the logic around the return in anilist-updater.ts
(the block that calls parseMediaInfo, buildGuessitTitle and returns
title/alternativeTitle/year/season/episode) to check fallback.confidence (or
equivalent) before assigning fallback.season/fallback.episode so low-confidence
parser guesses do not bypass the downstream guard in anilist-post-watch.ts.

---

Outside diff comments:
In `@plugin/subminer/process.lua`:
- Around line 298-303: The current branch disarms the startup gate
unconditionally when a duplicate auto-start arrives while state.overlay_running,
which lets playback resume early; change the logic so
disarm_auto_play_ready_gate() is not called for duplicate triggers when
state.overlay_running is true. Specifically, in the block handling
overrides.auto_start_trigger and state.overlay_running, remove or skip the call
to disarm_auto_play_ready_gate() (keep the log and socket_path resolution), and
only call disarm_auto_play_ready_gate() from the path that actually starts a new
overlay (the branch where state.overlay_running is false or where you initiate
startup), ensuring auto_start_pause_until_ready remains enforced for the active
startup cycle.

---

Nitpick comments:
In `@scripts/test-plugin-start-gate.lua`:
- Around line 909-925: Add a sibling test case in
scripts/test-plugin-start-gate.lua that simulates a second "file-loaded" firing
before the first "subminer-autoplay-ready" to verify that the pause-until-ready
gate stays armed until the ready event; locate the existing duplicate case near
the assertions using helpers like count_osd_message(recorded.osd, ...),
count_property_set(recorded.property_sets, "pause", ...), and the event names
"file-loaded" and "subminer-autoplay-ready", then create a new test sequence
that triggers: initial file-loaded -> pause should be set true, trigger a
duplicate file-loaded (pre-ready) -> assert pause remains true and no extra
ready/tokenization messages are emitted yet, then emit subminer-autoplay-ready
-> assert tokenization ready message appears once and pause is cleared once
(count_property_set "pause", false == 1).

In `@src/main/runtime/mpv-main-event-actions.test.ts`:
- Around line 196-224: The test currently only covers the success path for
createHandleMpvTimePosChangeHandler's maybeRunAnilistPostWatchUpdate; add a
counterpart that simulates maybeRunAnilistPostWatchUpdate rejecting and assert
that the supplied logError is called and the rest of the handlers still run in
order. Specifically, create a test that supplies a
maybeRunAnilistPostWatchUpdate which returns a rejected promise, a logError spy
that records calls, then invoke timeHandler({ time: ... }) followed by
pauseHandler({ paused: ... }) and assert the calls array includes the
'post-watch-error' entry (or that logError was called) in place of the success
'post-watch' and that subsequent progress/presence/pause handlers still execute
as before; use the same helper factories createHandleMpvTimePosChangeHandler and
createHandleMpvPauseChangeHandler to locate the code under test.

In `@src/renderer/subtitle-render.test.ts`:
- Around line 910-912: The test currently ensures no 'text-decoration-line:
underline' but misses shorthand 'text-decoration: underline'; update the
assertions around plainJlptBlock (used in subtitle-render.test) to also
assert.doesNotMatch(plainJlptBlock, /(?:^|\n)\s*text-decoration\s*:/) or a regex
specifically blocking 'text-decoration:\s*underline' so any shorthand underline
usage is rejected; modify the block where assert.doesNotMatch(plainJlptBlock,
/text-decoration-line:\s*underline;/) appears to include this additional
negative assertion referencing plainJlptBlock.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e173e4ec-d560-4c1e-95ac-42223551abd9

📥 Commits

Reviewing files that changed from the base of the PR and between 59f8de5 and a143ad1.

⛔ Files ignored due to path filters (14)
  • backlog/tasks/task-322 - Fix-failing-CI-checks-on-PR-57.md is excluded by !backlog/**
  • backlog/tasks/task-323 - Fix-macOS-overlay-hiding-while-mpv-remains-active.md is excluded by !backlog/**
  • backlog/tasks/task-324 - Fix-mpv-playlist-changes-re-running-app-warmups.md is excluded by !backlog/**
  • backlog/tasks/task-325 - Keep-JLPT-underline-color-fixed-with-combined-lookup-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-326 - Fix-AniList-post-watch-update-after-skipped-completion-threshold.md is excluded by !backlog/**
  • backlog/tasks/task-327 - Restore-stats-daemon-deferral-when-launching-playback.md is excluded by !backlog/**
  • backlog/tasks/task-328 - Keep-subtitle-prefetch-running-after-immediate-cached-annotation-render.md is excluded by !backlog/**
  • changes/319-interjection-annotation-filter.md is excluded by !changes/**
  • changes/323-macos-overlay-tracker-flaps.md is excluded by !changes/**
  • changes/324-mpv-playlist-overlay-reuse.md is excluded by !changes/**
  • changes/325-jlpt-combined-annotation-underlines.md is excluded by !changes/**
  • changes/326-anilist-time-position-post-watch.md is excluded by !changes/**
  • changes/327-stats-daemon-deferral.md is excluded by !changes/**
  • changes/328-subtitle-prefetch-cache-hit-resume.md is excluded by !changes/**
📒 Files selected for processing (19)
  • plugin/subminer/process.lua
  • scripts/test-plugin-session-bindings.lua
  • scripts/test-plugin-start-gate.lua
  • src/core/services/anilist/anilist-updater.test.ts
  • src/core/services/anilist/anilist-updater.ts
  • src/core/services/overlay-visibility.test.ts
  • src/core/services/overlay-visibility.ts
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/main/runtime/mpv-main-event-actions.test.ts
  • src/main/runtime/mpv-main-event-actions.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/main/runtime/stats-server-routing.test.ts
  • src/main/runtime/stats-server-routing.ts
  • src/renderer/handlers/mouse.test.ts
  • src/renderer/style.css
  • src/renderer/subtitle-render.test.ts
✅ Files skipped from review due to trivial changes (2)
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/core/services/tokenizer/annotation-stage.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/renderer/handlers/mouse.test.ts
  • src/core/services/tokenizer.ts
  • scripts/test-plugin-session-bindings.lua

Comment thread src/core/services/anilist/anilist-updater.ts Outdated
@ksyasuda ksyasuda force-pushed the tokenizer-updates branch from a143ad1 to 5f7c5f0 Compare May 4, 2026 04:27

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/anki-integration/known-word-cache.ts (1)

174-190: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Return value can miss cache-reset mutations.

Line 174 can clear cache state, but Lines 183/189 may still return false. Since downstream notifications now key off this boolean, real cache changes can be dropped.

Suggested fix
   appendFromNoteInfo(noteInfo: KnownWordCacheNoteInfo): boolean {
     if (!this.isKnownWordCacheEnabled() || !this.shouldAddMinedWordsImmediately()) {
       return false;
     }

+    let didMutateCache = false;
     const currentStateKey = this.getKnownWordCacheStateKey();
     if (this.knownWordsStateKey && this.knownWordsStateKey !== currentStateKey) {
+      didMutateCache = this.knownWords.size > 0 || this.noteWordsById.size > 0;
       this.clearKnownWordCacheState();
     }
     if (!this.knownWordsStateKey) {
       this.knownWordsStateKey = currentStateKey;
     }

     const preferredFields = this.getImmediateAppendFields();
     if (!preferredFields) {
-      return false;
+      return didMutateCache;
     }

     const nextWords = this.extractNormalizedKnownWordsFromNoteInfo(noteInfo, preferredFields);
     const changed = this.replaceNoteSnapshot(noteInfo.noteId, nextWords);
     if (!changed) {
-      return false;
+      return didMutateCache;
     }

     if (this.knownWordsLastRefreshedAtMs <= 0) {
       this.knownWordsLastRefreshedAtMs = Date.now();
     }
@@
-    return true;
+    return true;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/anki-integration/known-word-cache.ts` around lines 174 - 190, The code
may clear cache state via this.clearKnownWordCacheState() but still return false
later, dropping a real mutation; modify the logic in the method that contains
this.knownWordsStateKey checks so the result reflects cache mutations: introduce
a local changed flag (or set changed = true when this.clearKnownWordCacheState()
runs), then continue to compute nextWords and call
this.replaceNoteSnapshot(noteInfo.noteId, nextWords) and return the boolean OR
of the clear-induced change and replaceNoteSnapshot result. Ensure references to
knownWordsStateKey, clearKnownWordCacheState(), getImmediateAppendFields(),
extractNormalizedKnownWordsFromNoteInfo(), and replaceNoteSnapshot() are used to
locate and update the code.
src/main/runtime/anilist-post-watch.ts (1)

125-172: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Acquire inFlight before async gating to prevent duplicate AniList writes.

At Line 126 you read getInFlight(), but setInFlight(true) is only set at Line 172 after awaited work. Two concurrent calls can both pass the guard and race into duplicate AniList updates.

Proposed fix
 export function createMaybeRunAnilistPostWatchUpdateHandler(deps: {
@@
 }) {
   return async (options: AnilistPostWatchRunOptions = {}): Promise<void> => {
     if (deps.getInFlight()) {
       return;
     }
+    deps.setInFlight(true);
-    const force = options.force === true;
-
-    const resolved = deps.getResolvedConfig();
-    if (!deps.isAnilistTrackingEnabled(resolved)) {
-      return;
-    }
+    try {
+      const force = options.force === true;
+
+      const resolved = deps.getResolvedConfig();
+      if (!deps.isAnilistTrackingEnabled(resolved)) {
+        return;
+      }
@@
-    deps.setInFlight(true);
-    try {
       await deps.processNextAnilistRetryUpdate();
@@
-    } finally {
-      deps.setInFlight(false);
-    }
+    } finally {
+      deps.setInFlight(false);
+    }
   };
 }
🧹 Nitpick comments (1)
src/core/services/ipc.test.ts (1)

305-351: ⚡ Quick win

Add a rejection-path test for the manual-mark AniList callback.

Please add a case where runAnilistPostWatchUpdateOnManualMark throws and verify the handler still returns true for a successful mark. This will lock in failure isolation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/ipc.test.ts` around lines 305 - 351, Add a new test that
verifies when markActiveVideoWatched returns true but
runAnilistPostWatchUpdateOnManualMark throws, the IPC handler still returns true
and the thrown error is isolated; use the existing test setup
(createFakeIpcRegistrar, createRegisterIpcDeps, createFakeImmersionTracker) but
make runAnilistPostWatchUpdateOnManualMark async () => { throw new Error('..') }
and assert the result from
handlers.handle.get(IPC_CHANNELS.command.markActiveVideoWatched)?.({}) is true
and that calls include 'mark' (and optionally ensure 'anilist' is not required);
place this alongside the other registerIpcHandlers tests so it covers the
rejection path.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/services/ipc.ts`:
- Around line 390-395: The IPC handler for
IPC_CHANNELS.command.markActiveVideoWatched currently lets exceptions from
deps.runAnilistPostWatchUpdateOnManualMark propagate and fail the whole call;
change the handler so that after awaiting
deps.immersionTracker?.markActiveVideoWatched() you still return the boolean
result even if the post-watch sync fails by wrapping the call to
runAnilistPostWatchUpdateOnManualMark in a try/catch (or otherwise handling
rejection) and logging the error (do not rethrow), so markActiveVideoWatched()
success is never invalidated by runAnilistPostWatchUpdateOnManualMark failures.

---

Outside diff comments:
In `@src/anki-integration/known-word-cache.ts`:
- Around line 174-190: The code may clear cache state via
this.clearKnownWordCacheState() but still return false later, dropping a real
mutation; modify the logic in the method that contains this.knownWordsStateKey
checks so the result reflects cache mutations: introduce a local changed flag
(or set changed = true when this.clearKnownWordCacheState() runs), then continue
to compute nextWords and call this.replaceNoteSnapshot(noteInfo.noteId,
nextWords) and return the boolean OR of the clear-induced change and
replaceNoteSnapshot result. Ensure references to knownWordsStateKey,
clearKnownWordCacheState(), getImmediateAppendFields(),
extractNormalizedKnownWordsFromNoteInfo(), and replaceNoteSnapshot() are used to
locate and update the code.

---

Nitpick comments:
In `@src/core/services/ipc.test.ts`:
- Around line 305-351: Add a new test that verifies when markActiveVideoWatched
returns true but runAnilistPostWatchUpdateOnManualMark throws, the IPC handler
still returns true and the thrown error is isolated; use the existing test setup
(createFakeIpcRegistrar, createRegisterIpcDeps, createFakeImmersionTracker) but
make runAnilistPostWatchUpdateOnManualMark async () => { throw new Error('..') }
and assert the result from
handlers.handle.get(IPC_CHANNELS.command.markActiveVideoWatched)?.({}) is true
and that calls include 'mark' (and optionally ensure 'anilist' is not required);
place this alongside the other registerIpcHandlers tests so it covers the
rejection path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2af1ebd9-8289-4fed-8d46-38f01e435574

📥 Commits

Reviewing files that changed from the base of the PR and between a143ad1 and 5f7c5f0.

⛔ Files ignored due to path filters (48)
  • backlog/tasks/task-304 - Fix-N1-sentence-boundary-counting-across-Yomitan-punctuation-gaps.md is excluded by !backlog/**
  • backlog/tasks/task-305 - Use-Yomitan-word-classes-for-subtitle-token-POS-filtering.md is excluded by !backlog/**
  • backlog/tasks/task-306 - Fix-Hyprland-fullscreen-overlay-geometry-and-hover-pause.md is excluded by !backlog/**
  • backlog/tasks/task-307 - Exclude-kana-only-words-from-N1-subtitle-targets.md is excluded by !backlog/**
  • backlog/tasks/task-308 - Restore-persistent-JLPT-subtitle-underlines.md is excluded by !backlog/**
  • backlog/tasks/task-309 - Accept-modified-follow-up-digits-for-multi-line-sentence-mining.md is excluded by !backlog/**
  • backlog/tasks/task-310 - Suppress-N1-highlight-for-kana-only-candidate-sentences.md is excluded by !backlog/**
  • backlog/tasks/task-311 - Suppress-auxiliary-inflection-fragments-from-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-312 - Suppress-ja-nai-explanatory-ending-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-315 - Suppress-annotations-for-standalone-じゃない-and-です-ending-tokens.md is excluded by !backlog/**
  • backlog/tasks/task-316 - Fix-macOS-launcher-playback-exit-with-background-stats-daemon.md is excluded by !backlog/**
  • backlog/tasks/task-318 - Keep-JLPT-underline-color-fixed-after-lookup-selection.md is excluded by !backlog/**
  • backlog/tasks/task-319 - Suppress-annotations-for-expressive-interjection-subtitles.md is excluded by !backlog/**
  • backlog/tasks/task-320 - Refresh-current-subtitle-known-word-highlight-after-successful-mining.md is excluded by !backlog/**
  • backlog/tasks/task-321 - Preserve-word-audio-during-manual-clipboard-card-updates.md is excluded by !backlog/**
  • backlog/tasks/task-322 - Fix-failing-CI-checks-on-PR-57.md is excluded by !backlog/**
  • backlog/tasks/task-323 - Fix-macOS-overlay-hiding-while-mpv-remains-active.md is excluded by !backlog/**
  • backlog/tasks/task-324 - Fix-mpv-playlist-changes-re-running-app-warmups.md is excluded by !backlog/**
  • backlog/tasks/task-325 - Keep-JLPT-underline-color-fixed-with-combined-lookup-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-326 - Fix-AniList-post-watch-update-after-skipped-completion-threshold.md is excluded by !backlog/**
  • backlog/tasks/task-327 - Restore-stats-daemon-deferral-when-launching-playback.md is excluded by !backlog/**
  • backlog/tasks/task-328 - Keep-subtitle-prefetch-running-after-immediate-cached-annotation-render.md is excluded by !backlog/**
  • backlog/tasks/task-331 - Address-unresolved-CodeRabbit-comments-on-PR-57.md is excluded by !backlog/**
  • backlog/tasks/task-332 - Fix-subtitle-frequency-annotation-missing-ranks-shown-in-Yomitan-popup.md is excluded by !backlog/**
  • changes/305-tokenizer-word-class-pos-filtering.md is excluded by !changes/**
  • changes/306-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/307-kana-nplusone-targets.md is excluded by !changes/**
  • changes/308-jlpt-underlines.md is excluded by !changes/**
  • changes/309-multi-mine-modified-digits.md is excluded by !changes/**
  • changes/311-auxiliary-inflection-annotation-filter.md is excluded by !changes/**
  • changes/312-grammar-ending-annotation-filter.md is excluded by !changes/**
  • changes/316-macos-playback-stats-daemon.md is excluded by !changes/**
  • changes/318-jlpt-underline-selection.md is excluded by !changes/**
  • changes/319-interjection-annotation-filter.md is excluded by !changes/**
  • changes/320-current-subtitle-known-highlight.md is excluded by !changes/**
  • changes/321-grammar-ending-pattern-filter.md is excluded by !changes/**
  • changes/322-preserve-word-audio-manual-update.md is excluded by !changes/**
  • changes/323-macos-overlay-tracker-flaps.md is excluded by !changes/**
  • changes/324-mpv-playlist-overlay-reuse.md is excluded by !changes/**
  • changes/325-jlpt-combined-annotation-underlines.md is excluded by !changes/**
  • changes/326-anilist-time-position-post-watch.md is excluded by !changes/**
  • changes/327-stats-daemon-deferral.md is excluded by !changes/**
  • changes/328-subtitle-prefetch-cache-hit-resume.md is excluded by !changes/**
  • changes/332-subtitle-frequency-ordinal-prefix.md is excluded by !changes/**
  • docs-site/anki-integration.md is excluded by !docs-site/**
  • docs-site/configuration.md is excluded by !docs-site/**
  • docs-site/mining-workflow.md is excluded by !docs-site/**
  • docs-site/troubleshooting.md is excluded by !docs-site/**
📒 Files selected for processing (64)
  • package.json
  • plugin/subminer/lifecycle.lua
  • plugin/subminer/process.lua
  • plugin/subminer/session_bindings.lua
  • scripts/test-plugin-session-bindings.lua
  • scripts/test-plugin-start-gate.lua
  • src/anki-integration.test.ts
  • src/anki-integration.ts
  • src/anki-integration/card-creation-manual-update.test.ts
  • src/anki-integration/card-creation.ts
  • src/anki-integration/known-word-cache.ts
  • src/core/services/anilist/anilist-updater.test.ts
  • src/core/services/anilist/anilist-updater.ts
  • src/core/services/ipc.test.ts
  • src/core/services/ipc.ts
  • src/core/services/mpv-properties.ts
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/mpv-protocol.ts
  • src/core/services/mpv.test.ts
  • src/core/services/mpv.ts
  • src/core/services/overlay-visibility.test.ts
  • src/core/services/overlay-visibility.ts
  • src/core/services/overlay-window.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/grammar-ending.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/tokenizer/parser-selection-stage.test.ts
  • src/core/services/tokenizer/parser-selection-stage.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/main.ts
  • src/main/dependencies.ts
  • src/main/runtime/anilist-post-watch.test.ts
  • src/main/runtime/anilist-post-watch.ts
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/main/runtime/app-lifecycle-actions.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/main/runtime/mpv-main-event-actions.test.ts
  • src/main/runtime/mpv-main-event-actions.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/main/runtime/stats-server-routing.test.ts
  • src/main/runtime/stats-server-routing.ts
  • src/renderer/handlers/keyboard.test.ts
  • src/renderer/handlers/keyboard.ts
  • src/renderer/handlers/mouse.test.ts
  • src/renderer/handlers/mouse.ts
  • src/renderer/style.css
  • src/renderer/subtitle-render.test.ts
  • src/token-merger.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/window-trackers/hyprland-tracker.ts
✅ Files skipped from review due to trivial changes (19)
  • package.json
  • src/core/services/mpv.ts
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/main/runtime/stats-server-routing.ts
  • src/core/services/overlay-visibility.test.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/renderer/handlers/keyboard.test.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/core/services/mpv.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/core/services/tokenizer/parser-selection-stage.test.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/token-merger.ts
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/window-trackers/hyprland-tracker.ts
🚧 Files skipped from review as they are similar to previous changes (21)
  • src/main/runtime/stats-server-routing.test.ts
  • scripts/test-plugin-session-bindings.lua
  • src/anki-integration/card-creation-manual-update.test.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • plugin/subminer/session_bindings.lua
  • src/core/services/overlay-window.ts
  • src/anki-integration.test.ts
  • src/renderer/handlers/mouse.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/renderer/handlers/keyboard.ts
  • src/core/services/tokenizer/parser-selection-stage.ts
  • src/anki-integration/card-creation.ts
  • src/main/runtime/app-lifecycle-actions.ts
  • src/main/runtime/mpv-main-event-actions.ts
  • src/core/services/anilist/anilist-updater.test.ts
  • src/renderer/style.css
  • src/core/services/anilist/anilist-updater.ts
  • plugin/subminer/process.lua
  • src/anki-integration.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts

Comment thread src/core/services/ipc.ts
ksyasuda added a commit that referenced this pull request May 4, 2026
- Acquire AniList post-watch in-flight lock before async gating to prevent duplicate writes
- Isolate manual watched mark result from AniList post-watch callback failures
- Report known-word cache clears as mutations during immediate append when state existed
- Add regression tests for each fix

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/core/services/tokenizer/annotation-stage.test.ts (1)

518-679: 🏗️ Heavy lift

Split this new subtitle-filter matrix into focused or table-driven tests.

These additions are valuable, but keeping dozens of near-identical exclusion/preservation cases inline in one file will make future failures noisy and the file harder to navigate. Please move this matrix into a dedicated subtitle-filter spec or collapse the repeated setup/assertions behind shared helpers. As per coding guidelines, **/*.{ts,tsx}: TypeScript files should be kept small and follow existing patterns.

Also applies to: 814-1032, 1361-1854

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/tokenizer/annotation-stage.test.ts` around lines 518 - 679,
The new long matrix of near-duplicate tests for
shouldExcludeTokenFromSubtitleAnnotations should be extracted into a focused,
table-driven spec (e.g., subtitle-filter or subtitle-filter.spec) or collapsed
behind a shared helper; create a new test file and move these cases (including
the loops and uses of makeToken and assert.equal) into a single data-driven test
that iterates an array of {surface, headword, reading, partOfSpeech, pos1, pos2,
expected} entries, reusing makeToken and a small assertion helper, then delete
the inlined blocks from this file to keep each TypeScript test file small and
consistent with existing patterns.
src/core/services/tokenizer.test.ts (1)

3078-3155: 🏗️ Heavy lift

Break these new tokenizer regressions into focused suites.

The new coverage is good, but adding it here pushes even more unrelated scenarios into an already very large spec. Please split the word-class, subtitle-filter, and N+1 regressions into dedicated test files/helpers so the main tokenizer test remains navigable and failures map to one concern at a time. As per coding guidelines, **/*.{ts,tsx}: TypeScript files should be kept small and follow existing patterns.

Also applies to: 3233-3285, 4309-4512, 4733-4977

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/tokenizer.test.ts` around lines 3078 - 3155, The test file
has grown with multiple unrelated tokenizer scenarios—split the new word-class,
subtitle-filter, and N+1 regression tests into focused suites: create new test
files (e.g., tokenizer.wordclass.test.ts, tokenizer.subtitle-filter.test.ts,
tokenizer.nplusone.test.ts) and move the tests that call tokenizeSubtitle and
makeDepsFromYomitanTokens (the three shown and the other ranges noted) into the
appropriate new files; keep assertions referencing PartOfSpeech,
tokenizeSubtitle, and makeDepsFromYomitanTokens intact, and update any
imports/exports so each new file only contains the related tests to match
existing test patterns and keep each spec small and focused.
src/core/services/tokenizer/annotation-stage.ts (1)

152-203: 🏗️ Heavy lift

Extract the frequency exception rules out of annotation-stage.ts.

shouldAllowOrdinalPrefixNounFrequency is another surface/POS-specific escape hatch in a file that already owns annotation orchestration, JLPT lookup, known-word resolution, and subtitle filtering. Please move the frequency-allowance heuristics into a dedicated filter/rule module so future exceptions stay local and testable. As per coding guidelines, **/*.{ts,tsx}: TypeScript files should be kept small and follow existing patterns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/tokenizer/annotation-stage.ts` around lines 152 - 203, The
frequency-exemption heuristics (e.g., shouldAllowOrdinalPrefixNounFrequency)
need to be extracted from annotation-stage.ts into a dedicated module (suggest
name: frequencyRules or frequencyExemptions) so these surface/POS-specific
allowlists are isolated and testable; move shouldAllowOrdinalPrefixNounFrequency
and any related helpers (and their dependencies like splitNormalizedTagParts,
normalizePos1Tag/normalizePos2Tag usages) into that module, export a single
entrypoint (e.g., allowFrequencyForToken(token, pos1Exclusions, pos2Exclusions)
or specific predicate functions) and update isFrequencyExcludedByPos to call the
new exported function(s) instead of the in-file implementation while keeping
existing calls to isSingleKanaFrequencyNoiseToken,
shouldAllowContentLedMergedTokenFrequency, and isExcludedByTagSet unaffected;
add unit tests for the new module covering ordinal-prefix and content-led
merged-token cases.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/services/ipc.ts`:
- Around line 395-399: The catch block currently accesses (error as
Error).message which can throw if error is null/undefined; update the catch in
the markActiveVideoWatched/related handler in ipc.ts to safely derive the
message (e.g., check error instanceof Error and use error.message, otherwise
stringify or log the error value) and pass that safe string or the original
error to console.warn so logging itself cannot throw.

---

Nitpick comments:
In `@src/core/services/tokenizer.test.ts`:
- Around line 3078-3155: The test file has grown with multiple unrelated
tokenizer scenarios—split the new word-class, subtitle-filter, and N+1
regression tests into focused suites: create new test files (e.g.,
tokenizer.wordclass.test.ts, tokenizer.subtitle-filter.test.ts,
tokenizer.nplusone.test.ts) and move the tests that call tokenizeSubtitle and
makeDepsFromYomitanTokens (the three shown and the other ranges noted) into the
appropriate new files; keep assertions referencing PartOfSpeech,
tokenizeSubtitle, and makeDepsFromYomitanTokens intact, and update any
imports/exports so each new file only contains the related tests to match
existing test patterns and keep each spec small and focused.

In `@src/core/services/tokenizer/annotation-stage.test.ts`:
- Around line 518-679: The new long matrix of near-duplicate tests for
shouldExcludeTokenFromSubtitleAnnotations should be extracted into a focused,
table-driven spec (e.g., subtitle-filter or subtitle-filter.spec) or collapsed
behind a shared helper; create a new test file and move these cases (including
the loops and uses of makeToken and assert.equal) into a single data-driven test
that iterates an array of {surface, headword, reading, partOfSpeech, pos1, pos2,
expected} entries, reusing makeToken and a small assertion helper, then delete
the inlined blocks from this file to keep each TypeScript test file small and
consistent with existing patterns.

In `@src/core/services/tokenizer/annotation-stage.ts`:
- Around line 152-203: The frequency-exemption heuristics (e.g.,
shouldAllowOrdinalPrefixNounFrequency) need to be extracted from
annotation-stage.ts into a dedicated module (suggest name: frequencyRules or
frequencyExemptions) so these surface/POS-specific allowlists are isolated and
testable; move shouldAllowOrdinalPrefixNounFrequency and any related helpers
(and their dependencies like splitNormalizedTagParts,
normalizePos1Tag/normalizePos2Tag usages) into that module, export a single
entrypoint (e.g., allowFrequencyForToken(token, pos1Exclusions, pos2Exclusions)
or specific predicate functions) and update isFrequencyExcludedByPos to call the
new exported function(s) instead of the in-file implementation while keeping
existing calls to isSingleKanaFrequencyNoiseToken,
shouldAllowContentLedMergedTokenFrequency, and isExcludedByTagSet unaffected;
add unit tests for the new module covering ordinal-prefix and content-led
merged-token cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: d5b4eac4-9ec3-4032-acd7-7bd3bcbaee60

📥 Commits

Reviewing files that changed from the base of the PR and between 5f7c5f0 and 53c78f2.

⛔ Files ignored due to path filters (4)
  • backlog/tasks/task-333 - Suppress-aru-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-334 - Assess-and-address-PR-57-latest-CodeRabbit-comments.md is excluded by !backlog/**
  • changes/333-aru-annotation-filter.md is excluded by !changes/**
  • changes/334-coderabbit-followups.md is excluded by !changes/**
📒 Files selected for processing (10)
  • src/anki-integration/known-word-cache.test.ts
  • src/anki-integration/known-word-cache.ts
  • src/core/services/ipc.test.ts
  • src/core/services/ipc.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/main/runtime/anilist-post-watch.test.ts
  • src/main/runtime/anilist-post-watch.ts
✅ Files skipped from review due to trivial changes (1)
  • src/main/runtime/anilist-post-watch.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/anki-integration/known-word-cache.ts
  • src/core/services/ipc.test.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts

Comment thread src/core/services/ipc.ts
Comment on lines +395 to +399
} catch (error) {
console.warn(
'Failed to run AniList post-watch update after manual watched mark:',
(error as Error).message,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Harden catch logging to avoid re-throwing on non-Error values.

At Line 398, (error as Error).message can itself throw when error is null/undefined, which can still fail markActiveVideoWatched despite the try/catch intent.

Proposed fix
       } catch (error) {
+        const errorMessage = error instanceof Error ? error.message : String(error);
         console.warn(
           'Failed to run AniList post-watch update after manual watched mark:',
-          (error as Error).message,
+          errorMessage,
         );
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/ipc.ts` around lines 395 - 399, The catch block currently
accesses (error as Error).message which can throw if error is null/undefined;
update the catch in the markActiveVideoWatched/related handler in ipc.ts to
safely derive the message (e.g., check error instanceof Error and use
error.message, otherwise stringify or log the error value) and pass that safe
string or the original error to console.warn so logging itself cannot throw.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/services/hyprland-window-placement.ts`:
- Around line 135-151: The synchronous hyprctl calls use the local alias run
(set from options.execFileSync ?? execFileSync) without a timeout, so wrap both
invocations—when fetching clients (run('hyprctl', ['-j', 'clients'], { encoding:
'utf-8' })) and when dispatching placement args (run('hyprctl', args, { stdio:
'ignore' }))—to include a timeout option (e.g., timeout: <ms>) to avoid blocking
the Electron main thread; add a clear constant name (e.g., HYPRCTL_TIMEOUT_MS)
if one doesn’t exist and pass it into both run calls, preserving any existing
options (encoding/stdio) while merging the timeout, and apply the same pattern
in findHyprlandWindowForPlacement call sites and the similar calls in
src/window-trackers/hyprland-tracker.ts.

In `@src/core/services/tokenizer/annotation-stage.ts`:
- Around line 624-631: The code unconditionally clears known-state by returning
{ ...strippedToken, isKnown: false } after calling
sharedStripSubtitleAnnotationMetadata, which strips annotation metadata for
excluded kanji tokens; instead preserve the original token's known state. Fix by
returning strippedToken as-is (or explicitly set isKnown to token.isKnown)
rather than forcing false—update the return near the call to
sharedStripSubtitleAnnotationMetadata (variables: token, strippedToken, property
isKnown).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e204c32e-6232-479c-9d34-65b5134616ff

📥 Commits

Reviewing files that changed from the base of the PR and between 5b9bbae and cea3c20.

⛔ Files ignored due to path filters (15)
  • backlog/completed/task-336 - Fix-Hyprland-fullscreen-overlay-downward-offset.md is excluded by !backlog/**
  • backlog/completed/task-339 - Stop-pinning-Hyprland-overlay-windows.md is excluded by !backlog/**
  • backlog/tasks/task-337 - Fix-transient-Linux-safeStorage-failure-poisoning-AniList-token-store.md is excluded by !backlog/**
  • backlog/tasks/task-338 - Fix-known-word-highlight-on-standalone-subtitle-particles.md is excluded by !backlog/**
  • backlog/tasks/task-340 - Restore-default-replay-and-next-subtitle-overlay-keybindings.md is excluded by !backlog/**
  • changes/336-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/336-overlay-focus-ring.md is excluded by !changes/**
  • changes/337-anilist-safe-storage-retry.md is excluded by !changes/**
  • changes/338-known-word-particle-highlights.md is excluded by !changes/**
  • changes/339-hyprland-overlay-pin.md is excluded by !changes/**
  • changes/340-default-subtitle-keybindings.md is excluded by !changes/**
  • docs-site/configuration.md is excluded by !docs-site/**
  • docs-site/public/config.example.jsonc is excluded by !docs-site/**
  • docs-site/shortcuts.md is excluded by !docs-site/**
  • docs-site/usage.md is excluded by !docs-site/**
📒 Files selected for processing (30)
  • config.example.jsonc
  • package.json
  • plugin/subminer/session_bindings.lua
  • scripts/test-plugin-session-bindings.lua
  • src/config/definitions/defaults-core.ts
  • src/config/definitions/domain-registry.test.ts
  • src/core/services/anilist/anilist-token-store.test.ts
  • src/core/services/anilist/anilist-token-store.ts
  • src/core/services/hyprland-window-placement.test.ts
  • src/core/services/hyprland-window-placement.ts
  • src/core/services/mpv.test.ts
  • src/core/services/mpv.ts
  • src/core/services/overlay-manager.test.ts
  • src/core/services/overlay-window-config.test.ts
  • src/core/services/overlay-window-options.ts
  • src/core/services/overlay-window.ts
  • src/core/services/session-bindings.test.ts
  • src/core/services/stats-window-runtime.ts
  • src/core/services/stats-window.test.ts
  • src/core/services/stats-window.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/renderer/overlay-legacy-cleanup.test.ts
  • src/renderer/style.css
  • src/window-trackers/hyprland-tracker.test.ts
  • src/window-trackers/hyprland-tracker.ts
  • stats/src/App.tsx
  • stats/src/styles/globals.css
  • stats/src/styles/globals.test.ts
💤 Files with no reviewable changes (1)
  • src/core/services/anilist/anilist-token-store.ts
✅ Files skipped from review due to trivial changes (7)
  • src/core/services/overlay-window-config.test.ts
  • stats/src/styles/globals.test.ts
  • stats/src/App.tsx
  • src/config/definitions/domain-registry.test.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/core/services/overlay-manager.test.ts
  • src/window-trackers/hyprland-tracker.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • package.json
  • src/renderer/style.css

Comment on lines +135 to +151
const run = options.execFileSync ?? execFileSync;
try {
const clients = parseHyprlandClients(
String(run('hyprctl', ['-j', 'clients'], { encoding: 'utf-8' })),
);
const client = findHyprlandWindowForPlacement(clients, {
pid: options.pid ?? process.pid,
title: options.title,
});
if (!client) {
return false;
}

const dispatches = buildHyprlandPlacementDispatches(client, options.bounds);
for (const args of dispatches) {
run('hyprctl', args, { stdio: 'ignore' });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== hyprctl / execFileSync usage =="
rg -n --type=ts -C2 "hyprctl|execFileSync|ensureHyprlandWindowFloatingByTitle"

echo
echo "== Direct run(...) command option blocks in hyprland-window-placement.ts =="
rg -n --type=ts -C3 "run\\('hyprctl'" src/core/services/hyprland-window-placement.ts

Repository: ksyasuda/SubMiner

Length of output: 12538


🏁 Script executed:

#!/bin/bash

# Find all direct hyprctl invocations (execSync and execFileSync)
echo "=== All hyprctl calls with context ==="
rg -n "execSync|execFileSync" -A5 -B2 src/ --type=ts | grep -A5 -B2 "hyprctl"

echo ""
echo "=== Check Node.js version requirement ==="
if [ -f "package.json" ]; then
  jq '.engines.node // .engines // "not specified"' package.json
fi

echo ""
echo "=== Timeout option in tests ==="
rg -n "timeout" src/core/services/hyprland-window-placement.test.ts --type=ts

Repository: ksyasuda/SubMiner

Length of output: 1908


🏁 Script executed:

#!/bin/bash

# Check if there are existing examples of timeout usage in the codebase
echo "=== Existing timeout patterns in execFileSync/execSync ==="
rg -n "timeout\s*:" --type=ts | head -20

echo ""
echo "=== Check hyprland-tracker.ts for all hyprctl calls ==="
rg -n "hyprctl" src/window-trackers/hyprland-tracker.ts -A3 -B3 --type=ts

Repository: ksyasuda/SubMiner

Length of output: 2576


🌐 Web query:

Node.js execFileSync timeout option support version requirement

💡 Result:

The timeout option for child_process.execFileSync has been supported since Node.js v0.11.12, the version when execFileSync was added. This option is inherited from child_process.spawnSync, which also gained timeout support at the same time (v0.11.12). Official Node.js documentation for versions v16.x, v18.x, v20.x, v22.x, and v25.x all list timeout as an available option with default undefined, confirming long-standing support. A 2015 documentation commit references timeout behavior, and third-party sources like execa note it as a "long time" feature for sync methods predating async spawn support (added in v14+).

Citations:


Add timeouts to blocking hyprctl calls to prevent main-thread stalls.

Lines 138 and 150 in src/core/services/hyprland-window-placement.ts execute synchronous external commands without timeout protection. If hyprctl hangs, the Electron main thread will block indefinitely, freezing window operations. The timeout option is available on execFileSync and is already used elsewhere in the codebase (e.g., win32.ts:199).

Proposed fix
+const HYPRCTL_TIMEOUT_MS = 750;
+
 export function ensureHyprlandWindowFloatingByTitle(options: {
@@
   const run = options.execFileSync ?? execFileSync;
   try {
     const clients = parseHyprlandClients(
-      String(run('hyprctl', ['-j', 'clients'], { encoding: 'utf-8' })),
+      String(
+        run('hyprctl', ['-j', 'clients'], {
+          encoding: 'utf-8',
+          timeout: HYPRCTL_TIMEOUT_MS,
+          killSignal: 'SIGKILL',
+        }),
+      ),
     );
@@
     const dispatches = buildHyprlandPlacementDispatches(client, options.bounds);
     for (const args of dispatches) {
-      run('hyprctl', args, { stdio: 'ignore' });
+      run('hyprctl', args, {
+        stdio: 'ignore',
+        timeout: HYPRCTL_TIMEOUT_MS,
+        killSignal: 'SIGKILL',
+      });
     }

Note: Similar uncovered calls exist in src/window-trackers/hyprland-tracker.ts (lines 322 and 356) that should receive the same treatment.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/hyprland-window-placement.ts` around lines 135 - 151, The
synchronous hyprctl calls use the local alias run (set from options.execFileSync
?? execFileSync) without a timeout, so wrap both invocations—when fetching
clients (run('hyprctl', ['-j', 'clients'], { encoding: 'utf-8' })) and when
dispatching placement args (run('hyprctl', args, { stdio: 'ignore' }))—to
include a timeout option (e.g., timeout: <ms>) to avoid blocking the Electron
main thread; add a clear constant name (e.g., HYPRCTL_TIMEOUT_MS) if one doesn’t
exist and pass it into both run calls, preserving any existing options
(encoding/stdio) while merging the timeout, and apply the same pattern in
findHyprlandWindowForPlacement call sites and the similar calls in
src/window-trackers/hyprland-tracker.ts.

Comment thread src/core/services/tokenizer/annotation-stage.ts
ksyasuda added a commit that referenced this pull request May 4, 2026
- Acquire AniList post-watch in-flight lock before async gating to prevent duplicate writes
- Isolate manual watched mark result from AniList post-watch callback failures
- Report known-word cache clears as mutations during immediate append when state existed
- Add regression tests for each fix
@ksyasuda ksyasuda force-pushed the tokenizer-updates branch from cea3c20 to 4d5bf3d Compare May 4, 2026 07:06

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (4)
src/renderer/subtitle-render.test.ts (1)

922-927: ⚡ Quick win

Make the JLPT-only check tolerant of CSS rule grouping.

Requiring an empty extracted block is brittle; if this selector is later grouped into a shared rule, the test will false-fail even when no color override is introduced. Checking for the absence of text-color declarations is more stable.

Suggested test tweak
-    assert.equal(
-      jlptOnlyColorBlock,
-      '',
-      `word-jlpt-n${level} (without other annotations) must not set text color — JLPT only paints the underline`,
-    );
+    assert.doesNotMatch(
+      jlptOnlyColorBlock,
+      /(?:^|\n)\s*(?:color|-webkit-text-fill-color)\s*:/m,
+      `word-jlpt-n${level} (without other annotations) must not set text color — JLPT only paints the underline`,
+    );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/subtitle-render.test.ts` around lines 922 - 927, The test
currently asserts jlptOnlyColorBlock === '' which breaks if the selector is
grouped; instead assert that the extracted block (from
extractClassBlock(cssText, buildJlptColorSelector(level))) does not contain any
text color declarations: check jlptOnlyColorBlock for absence of patterns like
"color\s*:" (and optionally "fill\s*:" if SVG/text-fill is relevant) using a
regex-based negative assertion so grouping won't cause false failures; update
the assertion to use a doesNotMatch/doesNotContain-style assertion against that
regex and keep the rest of the test intact.
src/core/services/overlay-window.ts (1)

1-2: Align with codebase import style for consistency.

The default import import electron from 'electron' is safe in this project (esModuleInterop is enabled), but the majority of the codebase uses named imports instead. Switch to named imports to match the established pattern:

import { BrowserWindow, screen } from 'electron';
import type { Session } from 'electron';

Also applies to: 19-19

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/overlay-window.ts` around lines 1 - 2, Replace the default
import from 'electron' with named imports to match project style: import
BrowserWindow and screen as regular imports and keep Session as a type import;
update any other default-import occurrences (the second instance noted) to the
same pattern so all electron imports in this module use named imports instead of
the default electron export.
src/main.ts (2)

4269-4271: ⚡ Quick win

Deduplicate Anki callback wiring to avoid drift.

setKnownWordCacheUpdatedCallback is registered in two places with the same function. Extracting a single helper for Anki callback registration will reduce maintenance risk when callback setup changes.

♻️ Suggested refactor
+function attachAnkiIntegrationCallbacks(integration: AnkiIntegration | null): void {
+  integration?.setRecordCardsMinedCallback(recordTrackedCardsMined);
+  integration?.setKnownWordCacheUpdatedCallback(refreshCurrentSubtitleAfterKnownWordUpdate);
+}
+
 function initializeOverlayRuntime(): void {
   initializeOverlayRuntimeHandler();
-  appState.ankiIntegration?.setRecordCardsMinedCallback(recordTrackedCardsMined);
-  appState.ankiIntegration?.setKnownWordCacheUpdatedCallback(
-    refreshCurrentSubtitleAfterKnownWordUpdate,
-  );
+  attachAnkiIntegrationCallbacks(appState.ankiIntegration);
   syncOverlayMpvSubtitleSuppression();
 }
@@
       setAnkiIntegration: (integration: AnkiIntegration | null) => {
         appState.ankiIntegration = integration;
-        appState.ankiIntegration?.setRecordCardsMinedCallback(recordTrackedCardsMined);
-        appState.ankiIntegration?.setKnownWordCacheUpdatedCallback(
-          refreshCurrentSubtitleAfterKnownWordUpdate,
-        );
+        attachAnkiIntegrationCallbacks(appState.ankiIntegration);
       },

Also applies to: 4979-4981

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 4269 - 4271, Extract a small helper (e.g.,
registerAnkiKnownWordCallback) that takes appState (or appState.ankiIntegration)
and wires
appState.ankiIntegration.setKnownWordCacheUpdatedCallback(refreshCurrentSubtitleAfterKnownWordUpdate);
then replace both inline registrations with calls to this helper; reference the
existing symbols appState.ankiIntegration, setKnownWordCacheUpdatedCallback, and
refreshCurrentSubtitleAfterKnownWordUpdate so the single helper is used wherever
the callback was previously set.

1410-1424: 🏗️ Heavy lift

Consider extracting Linux fullscreen-refresh orchestration out of src/main.ts.

The new scheduling/cancellation flow is cohesive, but it adds more responsibility to an already very large entrypoint. Moving this orchestration behind a focused runtime module would better preserve maintainability.

As per coding guidelines, "**/*.{ts,tsx}: TypeScript files should be kept small and follow existing patterns".

Also applies to: 3862-3872, 5480-5511

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main.ts` around lines 1410 - 1424, The new scheduling/cancellation logic
(symbols: cancelLinuxMpvFullscreenOverlayRefreshBurst,
cancelPendingLinuxMpvFullscreenOverlayRefreshBurst,
clearScheduledSubtitlePrefetchRefresh, subtitlePrefetchRefreshTimer,
SEEK_THRESHOLD_SECONDS) should be extracted from src/main.ts into a focused
runtime module: create a new module (e.g., linuxFullscreenRefresh) that owns the
timers, the CancelLinuxMpvFullscreenOverlayRefreshBurst type and the three
functions, move the implementation there, export the public API (start/schedule,
cancelPendingLinuxMpvFullscreenOverlayRefreshBurst,
clearScheduledSubtitlePrefetchRefresh, and SEEK_THRESHOLD_SECONDS if widely
used), and update main.ts to import and call those exports; ensure no runtime
behavior changes and update any other places referenced in the review (lines
~3862-3872, ~5480-5511) to use the new module.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/core/services/overlay-window.ts`:
- Around line 56-59: The call to ensureHyprlandWindowFloatingByTitle is in the
hot geometry update path (after normalizeOverlayWindowBoundsForPlatform ->
window.setBounds) and makes synchronous hyprctl calls that can block the main
thread; to fix this, remove direct calls from that hot path and instead debounce
or defer them: e.g., replace the immediate ensureHyprlandWindowFloatingByTitle({
title: window.getTitle(), bounds }) with a debounced / scheduled invocation
(triggered only after a short idle period or on explicit rebind/layer
transitions), or move the call into the rebind/layer-change handler so
normalizeOverlayWindowBoundsForPlatform and window.setBounds remain fast and
non-blocking. Ensure the debounced function still uses window.getTitle() and the
computed bounds when invoked.

In `@src/renderer/subtitle-render.test.ts`:
- Around line 1105-1123: The test loop that asserts JLPT underline color locking
misses the 'word-name-match' annotation, so add 'word-name-match' to the array
of annotation classes checked in the for loop (the block that iterates over
['word-known', 'word-n-plus-one', ...]) so extractClassBlock(cssText,
`#subtitleRoot .word.word-jlpt-n${level}.${annotationClass}`) is also validated
for 'word-name-match' and the assert.match continues to verify the
text-decoration-color rule for that class.

---

Nitpick comments:
In `@src/core/services/overlay-window.ts`:
- Around line 1-2: Replace the default import from 'electron' with named imports
to match project style: import BrowserWindow and screen as regular imports and
keep Session as a type import; update any other default-import occurrences (the
second instance noted) to the same pattern so all electron imports in this
module use named imports instead of the default electron export.

In `@src/main.ts`:
- Around line 4269-4271: Extract a small helper (e.g.,
registerAnkiKnownWordCallback) that takes appState (or appState.ankiIntegration)
and wires
appState.ankiIntegration.setKnownWordCacheUpdatedCallback(refreshCurrentSubtitleAfterKnownWordUpdate);
then replace both inline registrations with calls to this helper; reference the
existing symbols appState.ankiIntegration, setKnownWordCacheUpdatedCallback, and
refreshCurrentSubtitleAfterKnownWordUpdate so the single helper is used wherever
the callback was previously set.
- Around line 1410-1424: The new scheduling/cancellation logic (symbols:
cancelLinuxMpvFullscreenOverlayRefreshBurst,
cancelPendingLinuxMpvFullscreenOverlayRefreshBurst,
clearScheduledSubtitlePrefetchRefresh, subtitlePrefetchRefreshTimer,
SEEK_THRESHOLD_SECONDS) should be extracted from src/main.ts into a focused
runtime module: create a new module (e.g., linuxFullscreenRefresh) that owns the
timers, the CancelLinuxMpvFullscreenOverlayRefreshBurst type and the three
functions, move the implementation there, export the public API (start/schedule,
cancelPendingLinuxMpvFullscreenOverlayRefreshBurst,
clearScheduledSubtitlePrefetchRefresh, and SEEK_THRESHOLD_SECONDS if widely
used), and update main.ts to import and call those exports; ensure no runtime
behavior changes and update any other places referenced in the review (lines
~3862-3872, ~5480-5511) to use the new module.

In `@src/renderer/subtitle-render.test.ts`:
- Around line 922-927: The test currently asserts jlptOnlyColorBlock === ''
which breaks if the selector is grouped; instead assert that the extracted block
(from extractClassBlock(cssText, buildJlptColorSelector(level))) does not
contain any text color declarations: check jlptOnlyColorBlock for absence of
patterns like "color\s*:" (and optionally "fill\s*:" if SVG/text-fill is
relevant) using a regex-based negative assertion so grouping won't cause false
failures; update the assertion to use a doesNotMatch/doesNotContain-style
assertion against that regex and keep the rest of the test intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ebb685a1-7fff-458b-bb15-473526b4592a

📥 Commits

Reviewing files that changed from the base of the PR and between cea3c20 and 4d5bf3d.

⛔ Files ignored due to path filters (68)
  • backlog/completed/task-336 - Fix-Hyprland-fullscreen-overlay-downward-offset.md is excluded by !backlog/**
  • backlog/completed/task-339 - Stop-pinning-Hyprland-overlay-windows.md is excluded by !backlog/**
  • backlog/tasks/task-304 - Fix-N1-sentence-boundary-counting-across-Yomitan-punctuation-gaps.md is excluded by !backlog/**
  • backlog/tasks/task-305 - Use-Yomitan-word-classes-for-subtitle-token-POS-filtering.md is excluded by !backlog/**
  • backlog/tasks/task-306 - Fix-Hyprland-fullscreen-overlay-geometry-and-hover-pause.md is excluded by !backlog/**
  • backlog/tasks/task-307 - Exclude-kana-only-words-from-N1-subtitle-targets.md is excluded by !backlog/**
  • backlog/tasks/task-308 - Restore-persistent-JLPT-subtitle-underlines.md is excluded by !backlog/**
  • backlog/tasks/task-309 - Accept-modified-follow-up-digits-for-multi-line-sentence-mining.md is excluded by !backlog/**
  • backlog/tasks/task-310 - Suppress-N1-highlight-for-kana-only-candidate-sentences.md is excluded by !backlog/**
  • backlog/tasks/task-311 - Suppress-auxiliary-inflection-fragments-from-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-312 - Suppress-ja-nai-explanatory-ending-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-315 - Suppress-annotations-for-standalone-じゃない-and-です-ending-tokens.md is excluded by !backlog/**
  • backlog/tasks/task-316 - Fix-macOS-launcher-playback-exit-with-background-stats-daemon.md is excluded by !backlog/**
  • backlog/tasks/task-318 - Keep-JLPT-underline-color-fixed-after-lookup-selection.md is excluded by !backlog/**
  • backlog/tasks/task-319 - Suppress-annotations-for-expressive-interjection-subtitles.md is excluded by !backlog/**
  • backlog/tasks/task-320 - Refresh-current-subtitle-known-word-highlight-after-successful-mining.md is excluded by !backlog/**
  • backlog/tasks/task-321 - Preserve-word-audio-during-manual-clipboard-card-updates.md is excluded by !backlog/**
  • backlog/tasks/task-322 - Fix-failing-CI-checks-on-PR-57.md is excluded by !backlog/**
  • backlog/tasks/task-323 - Fix-macOS-overlay-hiding-while-mpv-remains-active.md is excluded by !backlog/**
  • backlog/tasks/task-324 - Fix-mpv-playlist-changes-re-running-app-warmups.md is excluded by !backlog/**
  • backlog/tasks/task-325 - Keep-JLPT-underline-color-fixed-with-combined-lookup-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-326 - Fix-AniList-post-watch-update-after-skipped-completion-threshold.md is excluded by !backlog/**
  • backlog/tasks/task-327 - Restore-stats-daemon-deferral-when-launching-playback.md is excluded by !backlog/**
  • backlog/tasks/task-328 - Keep-subtitle-prefetch-running-after-immediate-cached-annotation-render.md is excluded by !backlog/**
  • backlog/tasks/task-331 - Address-unresolved-CodeRabbit-comments-on-PR-57.md is excluded by !backlog/**
  • backlog/tasks/task-332 - Fix-subtitle-frequency-annotation-missing-ranks-shown-in-Yomitan-popup.md is excluded by !backlog/**
  • backlog/tasks/task-333 - Suppress-aru-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-334 - Assess-and-address-PR-57-latest-CodeRabbit-comments.md is excluded by !backlog/**
  • backlog/tasks/task-335 - Fix-Linux-AniList-setup-gate-using-stored-keyring-token.md is excluded by !backlog/**
  • backlog/tasks/task-337 - Fix-transient-Linux-safeStorage-failure-poisoning-AniList-token-store.md is excluded by !backlog/**
  • backlog/tasks/task-338 - Fix-known-word-highlight-on-standalone-subtitle-particles.md is excluded by !backlog/**
  • backlog/tasks/task-340 - Restore-default-replay-and-next-subtitle-overlay-keybindings.md is excluded by !backlog/**
  • changes/305-tokenizer-word-class-pos-filtering.md is excluded by !changes/**
  • changes/306-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/307-kana-nplusone-targets.md is excluded by !changes/**
  • changes/308-jlpt-underlines.md is excluded by !changes/**
  • changes/309-multi-mine-modified-digits.md is excluded by !changes/**
  • changes/311-auxiliary-inflection-annotation-filter.md is excluded by !changes/**
  • changes/312-grammar-ending-annotation-filter.md is excluded by !changes/**
  • changes/316-macos-playback-stats-daemon.md is excluded by !changes/**
  • changes/318-jlpt-underline-selection.md is excluded by !changes/**
  • changes/319-interjection-annotation-filter.md is excluded by !changes/**
  • changes/320-current-subtitle-known-highlight.md is excluded by !changes/**
  • changes/321-grammar-ending-pattern-filter.md is excluded by !changes/**
  • changes/322-preserve-word-audio-manual-update.md is excluded by !changes/**
  • changes/323-macos-overlay-tracker-flaps.md is excluded by !changes/**
  • changes/324-mpv-playlist-overlay-reuse.md is excluded by !changes/**
  • changes/325-jlpt-combined-annotation-underlines.md is excluded by !changes/**
  • changes/326-anilist-time-position-post-watch.md is excluded by !changes/**
  • changes/327-stats-daemon-deferral.md is excluded by !changes/**
  • changes/328-subtitle-prefetch-cache-hit-resume.md is excluded by !changes/**
  • changes/332-subtitle-frequency-ordinal-prefix.md is excluded by !changes/**
  • changes/333-aru-annotation-filter.md is excluded by !changes/**
  • changes/334-coderabbit-followups.md is excluded by !changes/**
  • changes/335-anilist-linux-token-setup.md is excluded by !changes/**
  • changes/336-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/336-overlay-focus-ring.md is excluded by !changes/**
  • changes/337-anilist-safe-storage-retry.md is excluded by !changes/**
  • changes/338-known-word-particle-highlights.md is excluded by !changes/**
  • changes/339-hyprland-overlay-pin.md is excluded by !changes/**
  • changes/340-default-subtitle-keybindings.md is excluded by !changes/**
  • docs-site/anki-integration.md is excluded by !docs-site/**
  • docs-site/configuration.md is excluded by !docs-site/**
  • docs-site/mining-workflow.md is excluded by !docs-site/**
  • docs-site/public/config.example.jsonc is excluded by !docs-site/**
  • docs-site/shortcuts.md is excluded by !docs-site/**
  • docs-site/troubleshooting.md is excluded by !docs-site/**
  • docs-site/usage.md is excluded by !docs-site/**
📒 Files selected for processing (91)
  • config.example.jsonc
  • package.json
  • plugin/subminer/lifecycle.lua
  • plugin/subminer/process.lua
  • plugin/subminer/session_bindings.lua
  • scripts/test-plugin-session-bindings.lua
  • scripts/test-plugin-start-gate.lua
  • src/anki-integration.test.ts
  • src/anki-integration.ts
  • src/anki-integration/card-creation-manual-update.test.ts
  • src/anki-integration/card-creation.ts
  • src/anki-integration/known-word-cache.test.ts
  • src/anki-integration/known-word-cache.ts
  • src/config/definitions/defaults-core.ts
  • src/config/definitions/domain-registry.test.ts
  • src/core/services/anilist/anilist-token-store.test.ts
  • src/core/services/anilist/anilist-token-store.ts
  • src/core/services/anilist/anilist-updater.test.ts
  • src/core/services/anilist/anilist-updater.ts
  • src/core/services/hyprland-window-placement.test.ts
  • src/core/services/hyprland-window-placement.ts
  • src/core/services/ipc.test.ts
  • src/core/services/ipc.ts
  • src/core/services/mpv-properties.ts
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/mpv-protocol.ts
  • src/core/services/mpv.test.ts
  • src/core/services/mpv.ts
  • src/core/services/overlay-manager.test.ts
  • src/core/services/overlay-visibility.test.ts
  • src/core/services/overlay-visibility.ts
  • src/core/services/overlay-window-config.test.ts
  • src/core/services/overlay-window-options.ts
  • src/core/services/overlay-window.ts
  • src/core/services/session-bindings.test.ts
  • src/core/services/stats-window-runtime.ts
  • src/core/services/stats-window.test.ts
  • src/core/services/stats-window.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/grammar-ending.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/tokenizer/parser-selection-stage.test.ts
  • src/core/services/tokenizer/parser-selection-stage.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/main.ts
  • src/main/dependencies.ts
  • src/main/runtime/anilist-post-watch.test.ts
  • src/main/runtime/anilist-post-watch.ts
  • src/main/runtime/anilist-setup-protocol-main-deps.test.ts
  • src/main/runtime/anilist-setup-protocol.test.ts
  • src/main/runtime/anilist-setup-protocol.ts
  • src/main/runtime/anilist-setup.test.ts
  • src/main/runtime/anilist-setup.ts
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/main/runtime/app-lifecycle-actions.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/main/runtime/composers/anilist-setup-composer.test.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/main/runtime/mpv-main-event-actions.test.ts
  • src/main/runtime/mpv-main-event-actions.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/main/runtime/startup-config.test.ts
  • src/main/runtime/startup-config.ts
  • src/main/runtime/stats-server-routing.test.ts
  • src/main/runtime/stats-server-routing.ts
  • src/renderer/handlers/keyboard.test.ts
  • src/renderer/handlers/keyboard.ts
  • src/renderer/handlers/mouse.test.ts
  • src/renderer/handlers/mouse.ts
  • src/renderer/overlay-legacy-cleanup.test.ts
  • src/renderer/style.css
  • src/renderer/subtitle-render.test.ts
  • src/token-merger.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/window-trackers/hyprland-tracker.ts
  • stats/src/App.tsx
  • stats/src/styles/globals.css
  • stats/src/styles/globals.test.ts
💤 Files with no reviewable changes (1)
  • src/core/services/anilist/anilist-token-store.ts
✅ Files skipped from review due to trivial changes (38)
  • stats/src/styles/globals.test.ts
  • src/main/runtime/composers/anilist-setup-composer.test.ts
  • src/main/runtime/stats-server-routing.test.ts
  • stats/src/App.tsx
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/core/services/stats-window-runtime.ts
  • src/main/runtime/anilist-setup-protocol-main-deps.test.ts
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/main/runtime/stats-server-routing.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/renderer/handlers/keyboard.test.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/core/services/stats-window.test.ts
  • src/config/definitions/domain-registry.test.ts
  • src/core/services/session-bindings.test.ts
  • src/renderer/handlers/keyboard.ts
  • src/main/runtime/anilist-setup.test.ts
  • src/anki-integration.ts
  • src/anki-integration.test.ts
  • src/core/services/anilist/anilist-updater.test.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/core/services/mpv.ts
  • src/core/services/stats-window.ts
  • src/core/services/overlay-manager.test.ts
  • config.example.jsonc
  • src/main/runtime/mpv-main-event-actions.test.ts
  • src/core/services/tokenizer.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/main/runtime/startup-config.test.ts
  • src/window-trackers/hyprland-tracker.ts
  • src/core/services/tokenizer/grammar-ending.ts
  • src/core/services/ipc.test.ts
  • stats/src/styles/globals.css
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
🚧 Files skipped from review as they are similar to previous changes (35)
  • src/core/services/overlay-window-config.test.ts
  • src/core/services/overlay-window-options.ts
  • plugin/subminer/lifecycle.lua
  • src/main/runtime/app-lifecycle-actions.ts
  • src/core/services/anilist/anilist-updater.ts
  • src/core/services/ipc.ts
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/main/runtime/startup-config.ts
  • src/renderer/overlay-legacy-cleanup.test.ts
  • src/core/services/mpv-protocol.ts
  • src/main/runtime/mpv-main-event-actions.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/main/runtime/anilist-setup-protocol.test.ts
  • src/main/runtime/anilist-setup.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/renderer/handlers/mouse.ts
  • src/core/services/hyprland-window-placement.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/hyprland-window-placement.test.ts
  • src/main/runtime/anilist-setup-protocol.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/config/definitions/defaults-core.ts
  • scripts/test-plugin-session-bindings.lua
  • src/anki-integration/known-word-cache.test.ts
  • src/token-merger.ts
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/anki-integration/card-creation.ts
  • src/main/runtime/anilist-post-watch.test.ts
  • package.json
  • plugin/subminer/process.lua
  • src/renderer/style.css
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/renderer/handlers/mouse.test.ts
  • src/core/services/tokenizer/annotation-stage.test.ts

Comment on lines +56 to 59
const bounds = normalizeOverlayWindowBoundsForPlatform(geometry, process.platform, screen);
window.setBounds(bounds);
ensureHyprlandWindowFloatingByTitle({ title: window.getTitle(), bounds });
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid synchronous Hyprland placement calls on every bounds update.

Line 58 pulls ensureHyprlandWindowFloatingByTitle into the geometry hot path; that helper performs synchronous hyprctl calls, which can block the Electron main thread during frequent resize/move updates. Please debounce/collapse this path or trigger it only on explicit rebind/layer transitions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/core/services/overlay-window.ts` around lines 56 - 59, The call to
ensureHyprlandWindowFloatingByTitle is in the hot geometry update path (after
normalizeOverlayWindowBoundsForPlatform -> window.setBounds) and makes
synchronous hyprctl calls that can block the main thread; to fix this, remove
direct calls from that hot path and instead debounce or defer them: e.g.,
replace the immediate ensureHyprlandWindowFloatingByTitle({ title:
window.getTitle(), bounds }) with a debounced / scheduled invocation (triggered
only after a short idle period or on explicit rebind/layer transitions), or move
the call into the rebind/layer-change handler so
normalizeOverlayWindowBoundsForPlatform and window.setBounds remain fast and
non-blocking. Ensure the debounced function still uses window.getTitle() and the
computed bounds when invoked.

Comment thread src/renderer/subtitle-render.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/core/services/tokenizer.test.ts (1)

4233-5067: 🏗️ Heavy lift

Consider table-driving or splitting these exclusion-rule cases.

This block adds many near-identical fixtures and token-summary assertions that mostly vary by inputs and expected cleared fields. A small shared helper plus a case matrix, or a dedicated subtitle-annotation test file, would make future rule changes much easier to maintain in this already very large TypeScript test file.

As per coding guidelines, **/*.{ts,tsx}: TypeScript files should be kept small and follow existing patterns.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/services/tokenizer.test.ts` around lines 4233 - 5067, The tests
repeat the same pattern around tokenizeSubtitle and makeDepsFromYomitanTokens
with only inputs and expected cleared fields changing; refactor into a
table-driven helper to reduce duplication: create a shared helper (e.g.,
runAnnotationClearCase) that calls tokenizeSubtitle with a provided token
fixture from makeDepsFromYomitanTokens, computes tokenSummary, and asserts
expected entries, then replace each duplicated test with a lightweight row in a
case matrix (surface/input, mecab tokens, deps overrides, expectations).
Optionally move these cases into a new test module to keep the original file
small; ensure helper references include tokenizeSubtitle,
makeDepsFromYomitanTokens and tokenSummary to locate where to update.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/services/tokenizer.test.ts`:
- Around line 3233-3285: The test incorrectly asserts that the kanji token '点'
should have isKnown cleared; update the assertion in the tokenizeSubtitle test
so the excluded-kanji case preserves isKnown for the '点' token (change the
expectation on result.tokens?.[1]?.isKnown to true), and ensure any related
assertions about frequencyRank and jlptLevel remain consistent with the
preserved-known behavior; locate this check in the test around the
tokenizeSubtitle invocation and the assertions for result.tokens?.[1].

---

Nitpick comments:
In `@src/core/services/tokenizer.test.ts`:
- Around line 4233-5067: The tests repeat the same pattern around
tokenizeSubtitle and makeDepsFromYomitanTokens with only inputs and expected
cleared fields changing; refactor into a table-driven helper to reduce
duplication: create a shared helper (e.g., runAnnotationClearCase) that calls
tokenizeSubtitle with a provided token fixture from makeDepsFromYomitanTokens,
computes tokenSummary, and asserts expected entries, then replace each
duplicated test with a lightweight row in a case matrix (surface/input, mecab
tokens, deps overrides, expectations). Optionally move these cases into a new
test module to keep the original file small; ensure helper references include
tokenizeSubtitle, makeDepsFromYomitanTokens and tokenSummary to locate where to
update.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7ba20e81-5736-48b9-a4f4-0095fce55be6

📥 Commits

Reviewing files that changed from the base of the PR and between 4d5bf3d and 4cb7b53.

⛔ Files ignored due to path filters (1)
  • backlog/tasks/task-341 - Fix-frequency-highlight-for-honorific-prefix-noun-tokens.md is excluded by !backlog/**
📒 Files selected for processing (2)
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/core/services/tokenizer/annotation-stage.ts

Comment on lines +3233 to +3285
test('tokenizeSubtitle clears known-word highlight for exact non-independent kanji noun tokens', async () => {
const result = await tokenizeSubtitle(
'その点',
makeDepsFromYomitanTokens(
[
{ surface: 'その', reading: 'その', headword: 'その' },
{ surface: '点', reading: 'てん', headword: '点' },
],
{
isKnownWord: (text) => text === '点' || text === 'てん',
getFrequencyDictionaryEnabled: () => true,
getFrequencyRank: (text) => (text === '点' ? 1384 : null),
getJlptLevel: (text) => (text === '点' ? 'N3' : null),
tokenizeWithMecab: async () => [
{
headword: 'その',
surface: 'その',
reading: 'ソノ',
startPos: 0,
endPos: 2,
partOfSpeech: PartOfSpeech.other,
pos1: '連体詞',
isMerged: false,
isKnown: false,
isNPlusOneTarget: false,
},
{
headword: '点',
surface: '点',
reading: 'テン',
startPos: 2,
endPos: 3,
partOfSpeech: PartOfSpeech.noun,
pos1: '名詞',
pos2: '非自立',
pos3: '一般',
isMerged: false,
isKnown: false,
isNPlusOneTarget: false,
},
],
},
),
);

assert.equal(result.tokens?.length, 2);
assert.equal(result.tokens?.[0]?.isKnown, false);
assert.equal(result.tokens?.[1]?.surface, '点');
assert.equal(result.tokens?.[1]?.isKnown, false);
assert.equal(result.tokens?.[1]?.isNPlusOneTarget, false);
assert.equal(result.tokens?.[1]?.frequencyRank, undefined);
assert.equal(result.tokens?.[1]?.jlptLevel, undefined);
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Preserve isKnown for excluded kanji token .

Line 3281 expects isKnown to be cleared, but this is the exact excluded-kanji case the PR is supposed to preserve. Keeping this assertion as false would let a regression slip through on the new isKnown retention behavior.

Proposed test fix
-test('tokenizeSubtitle clears known-word highlight for exact non-independent kanji noun tokens', async () => {
+test('tokenizeSubtitle preserves known-word highlight for exact non-independent kanji noun tokens', async () => {
@@
-  assert.equal(result.tokens?.[1]?.isKnown, false);
+  assert.equal(result.tokens?.[1]?.isKnown, true);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/services/tokenizer.test.ts` around lines 3233 - 3285, The test
incorrectly asserts that the kanji token '点' should have isKnown cleared; update
the assertion in the tokenizeSubtitle test so the excluded-kanji case preserves
isKnown for the '点' token (change the expectation on result.tokens?.[1]?.isKnown
to true), and ensure any related assertions about frequencyRank and jlptLevel
remain consistent with the preserved-known behavior; locate this check in the
test around the tokenizeSubtitle invocation and the assertions for
result.tokens?.[1].

ksyasuda added 11 commits May 10, 2026 22:19
- Acquire AniList post-watch in-flight lock before async gating to prevent duplicate writes
- Isolate manual watched mark result from AniList post-watch callback failures
- Report known-word cache clears as mutations during immediate append when state existed
- Add regression tests for each fix
- Gate setup success on token persistence: `saveToken` now returns `boolean`; on failure, keeps the setup window open instead of reporting success
- Config reload passes `allowSetupPrompt: false` so playback reloads don't re-open the setup window
- Add regression test for persistence-failure path
- Force-apply exact Hyprland move/resize/setprop dispatches when bounds are provided
- Stop pinning overlay windows; toggle pin off when Hyprland reports pinned=true
- Compensate stats overlay outer placement for Electron/Wayland content insets
- Make stats overlay window and page opaque so mpv cannot show through transparent insets
- Constrain stats app to h-screen with internal scroll so content covers mpv from y=0
- Lock overlay/stats window titles against page-title-updated events
- Add regression coverage for placement dispatches, inset compensation, and CSS overlay mode
- Add `shouldAllowHonorificPrefixNounFrequency` to exempt お/ご/御 + noun merged tokens from frequency exclusion
- Add regression test for `ご機嫌` asserting rank 5484 is preserved after MeCab enrichment and annotation
- Close TASK-341
@ksyasuda ksyasuda force-pushed the tokenizer-updates branch from 4cb7b53 to 0b72fa1 Compare May 11, 2026 05:53

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/renderer/subtitle-render.test.ts (1)

1105-1110: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Cover the word-name-match + JLPT combination here too.

Line 1105 still skips word-name-match, so regressions in that higher-priority annotation path can slip past this underline-lock test.

Suggested change
     for (const annotationClass of [
       'word-known',
       'word-n-plus-one',
+      'word-name-match',
       'word-frequency-single',
       'word-frequency-band-2',
     ]) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/subtitle-render.test.ts` around lines 1105 - 1110, The test loop
skipping the 'word-name-match' path can miss regressions; update the for-loop
that iterates over annotationClass (in subtitle-render.test.ts) to include
'word-name-match' and add a test case for the JLPT combination (i.e., ensure the
same loop or an adjacent assertion verifies the underline-lock behavior when
annotationClass === 'word-name-match' combined with a JLPT-level annotation), so
the higher-priority name-match + JLPT scenario is covered by the underline-lock
test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/renderer/subtitle-render.test.ts`:
- Around line 1105-1110: The test loop skipping the 'word-name-match' path can
miss regressions; update the for-loop that iterates over annotationClass (in
subtitle-render.test.ts) to include 'word-name-match' and add a test case for
the JLPT combination (i.e., ensure the same loop or an adjacent assertion
verifies the underline-lock behavior when annotationClass === 'word-name-match'
combined with a JLPT-level annotation), so the higher-priority name-match + JLPT
scenario is covered by the underline-lock test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 95fb126a-ae67-4cc6-a59b-8a230d582e9b

📥 Commits

Reviewing files that changed from the base of the PR and between 4cb7b53 and 0b72fa1.

⛔ Files ignored due to path filters (69)
  • backlog/completed/task-336 - Fix-Hyprland-fullscreen-overlay-downward-offset.md is excluded by !backlog/**
  • backlog/completed/task-339 - Stop-pinning-Hyprland-overlay-windows.md is excluded by !backlog/**
  • backlog/tasks/task-304 - Fix-N1-sentence-boundary-counting-across-Yomitan-punctuation-gaps.md is excluded by !backlog/**
  • backlog/tasks/task-305 - Use-Yomitan-word-classes-for-subtitle-token-POS-filtering.md is excluded by !backlog/**
  • backlog/tasks/task-306 - Fix-Hyprland-fullscreen-overlay-geometry-and-hover-pause.md is excluded by !backlog/**
  • backlog/tasks/task-307 - Exclude-kana-only-words-from-N1-subtitle-targets.md is excluded by !backlog/**
  • backlog/tasks/task-308 - Restore-persistent-JLPT-subtitle-underlines.md is excluded by !backlog/**
  • backlog/tasks/task-309 - Accept-modified-follow-up-digits-for-multi-line-sentence-mining.md is excluded by !backlog/**
  • backlog/tasks/task-310 - Suppress-N1-highlight-for-kana-only-candidate-sentences.md is excluded by !backlog/**
  • backlog/tasks/task-311 - Suppress-auxiliary-inflection-fragments-from-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-312 - Suppress-ja-nai-explanatory-ending-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-315 - Suppress-annotations-for-standalone-じゃない-and-です-ending-tokens.md is excluded by !backlog/**
  • backlog/tasks/task-316 - Fix-macOS-launcher-playback-exit-with-background-stats-daemon.md is excluded by !backlog/**
  • backlog/tasks/task-318 - Keep-JLPT-underline-color-fixed-after-lookup-selection.md is excluded by !backlog/**
  • backlog/tasks/task-319 - Suppress-annotations-for-expressive-interjection-subtitles.md is excluded by !backlog/**
  • backlog/tasks/task-320 - Refresh-current-subtitle-known-word-highlight-after-successful-mining.md is excluded by !backlog/**
  • backlog/tasks/task-321 - Preserve-word-audio-during-manual-clipboard-card-updates.md is excluded by !backlog/**
  • backlog/tasks/task-322 - Fix-failing-CI-checks-on-PR-57.md is excluded by !backlog/**
  • backlog/tasks/task-323 - Fix-macOS-overlay-hiding-while-mpv-remains-active.md is excluded by !backlog/**
  • backlog/tasks/task-324 - Fix-mpv-playlist-changes-re-running-app-warmups.md is excluded by !backlog/**
  • backlog/tasks/task-325 - Keep-JLPT-underline-color-fixed-with-combined-lookup-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-326 - Fix-AniList-post-watch-update-after-skipped-completion-threshold.md is excluded by !backlog/**
  • backlog/tasks/task-327 - Restore-stats-daemon-deferral-when-launching-playback.md is excluded by !backlog/**
  • backlog/tasks/task-328 - Keep-subtitle-prefetch-running-after-immediate-cached-annotation-render.md is excluded by !backlog/**
  • backlog/tasks/task-331 - Address-unresolved-CodeRabbit-comments-on-PR-57.md is excluded by !backlog/**
  • backlog/tasks/task-332 - Fix-subtitle-frequency-annotation-missing-ranks-shown-in-Yomitan-popup.md is excluded by !backlog/**
  • backlog/tasks/task-333 - Suppress-aru-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-334 - Assess-and-address-PR-57-latest-CodeRabbit-comments.md is excluded by !backlog/**
  • backlog/tasks/task-335 - Fix-Linux-AniList-setup-gate-using-stored-keyring-token.md is excluded by !backlog/**
  • backlog/tasks/task-337 - Fix-transient-Linux-safeStorage-failure-poisoning-AniList-token-store.md is excluded by !backlog/**
  • backlog/tasks/task-338 - Fix-known-word-highlight-on-standalone-subtitle-particles.md is excluded by !backlog/**
  • backlog/tasks/task-340 - Restore-default-replay-and-next-subtitle-overlay-keybindings.md is excluded by !backlog/**
  • backlog/tasks/task-341 - Fix-frequency-highlight-for-honorific-prefix-noun-tokens.md is excluded by !backlog/**
  • changes/305-tokenizer-word-class-pos-filtering.md is excluded by !changes/**
  • changes/306-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/307-kana-nplusone-targets.md is excluded by !changes/**
  • changes/308-jlpt-underlines.md is excluded by !changes/**
  • changes/309-multi-mine-modified-digits.md is excluded by !changes/**
  • changes/311-auxiliary-inflection-annotation-filter.md is excluded by !changes/**
  • changes/312-grammar-ending-annotation-filter.md is excluded by !changes/**
  • changes/316-macos-playback-stats-daemon.md is excluded by !changes/**
  • changes/318-jlpt-underline-selection.md is excluded by !changes/**
  • changes/319-interjection-annotation-filter.md is excluded by !changes/**
  • changes/320-current-subtitle-known-highlight.md is excluded by !changes/**
  • changes/321-grammar-ending-pattern-filter.md is excluded by !changes/**
  • changes/322-preserve-word-audio-manual-update.md is excluded by !changes/**
  • changes/323-macos-overlay-tracker-flaps.md is excluded by !changes/**
  • changes/324-mpv-playlist-overlay-reuse.md is excluded by !changes/**
  • changes/325-jlpt-combined-annotation-underlines.md is excluded by !changes/**
  • changes/326-anilist-time-position-post-watch.md is excluded by !changes/**
  • changes/327-stats-daemon-deferral.md is excluded by !changes/**
  • changes/328-subtitle-prefetch-cache-hit-resume.md is excluded by !changes/**
  • changes/332-subtitle-frequency-ordinal-prefix.md is excluded by !changes/**
  • changes/333-aru-annotation-filter.md is excluded by !changes/**
  • changes/334-coderabbit-followups.md is excluded by !changes/**
  • changes/335-anilist-linux-token-setup.md is excluded by !changes/**
  • changes/336-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/336-overlay-focus-ring.md is excluded by !changes/**
  • changes/337-anilist-safe-storage-retry.md is excluded by !changes/**
  • changes/338-known-word-particle-highlights.md is excluded by !changes/**
  • changes/339-hyprland-overlay-pin.md is excluded by !changes/**
  • changes/340-default-subtitle-keybindings.md is excluded by !changes/**
  • docs-site/anki-integration.md is excluded by !docs-site/**
  • docs-site/configuration.md is excluded by !docs-site/**
  • docs-site/mining-workflow.md is excluded by !docs-site/**
  • docs-site/public/config.example.jsonc is excluded by !docs-site/**
  • docs-site/shortcuts.md is excluded by !docs-site/**
  • docs-site/troubleshooting.md is excluded by !docs-site/**
  • docs-site/usage.md is excluded by !docs-site/**
📒 Files selected for processing (91)
  • config.example.jsonc
  • package.json
  • plugin/subminer/lifecycle.lua
  • plugin/subminer/process.lua
  • plugin/subminer/session_bindings.lua
  • scripts/test-plugin-session-bindings.lua
  • scripts/test-plugin-start-gate.lua
  • src/anki-integration.test.ts
  • src/anki-integration.ts
  • src/anki-integration/card-creation-manual-update.test.ts
  • src/anki-integration/card-creation.ts
  • src/anki-integration/known-word-cache.test.ts
  • src/anki-integration/known-word-cache.ts
  • src/config/definitions/defaults-core.ts
  • src/config/definitions/domain-registry.test.ts
  • src/core/services/anilist/anilist-token-store.test.ts
  • src/core/services/anilist/anilist-token-store.ts
  • src/core/services/anilist/anilist-updater.test.ts
  • src/core/services/anilist/anilist-updater.ts
  • src/core/services/hyprland-window-placement.test.ts
  • src/core/services/hyprland-window-placement.ts
  • src/core/services/ipc.test.ts
  • src/core/services/ipc.ts
  • src/core/services/mpv-properties.ts
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/mpv-protocol.ts
  • src/core/services/mpv.test.ts
  • src/core/services/mpv.ts
  • src/core/services/overlay-manager.test.ts
  • src/core/services/overlay-visibility.test.ts
  • src/core/services/overlay-visibility.ts
  • src/core/services/overlay-window-config.test.ts
  • src/core/services/overlay-window-options.ts
  • src/core/services/overlay-window.ts
  • src/core/services/session-bindings.test.ts
  • src/core/services/stats-window-runtime.ts
  • src/core/services/stats-window.test.ts
  • src/core/services/stats-window.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/grammar-ending.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/tokenizer/parser-selection-stage.test.ts
  • src/core/services/tokenizer/parser-selection-stage.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/main.ts
  • src/main/dependencies.ts
  • src/main/runtime/anilist-post-watch.test.ts
  • src/main/runtime/anilist-post-watch.ts
  • src/main/runtime/anilist-setup-protocol-main-deps.test.ts
  • src/main/runtime/anilist-setup-protocol.test.ts
  • src/main/runtime/anilist-setup-protocol.ts
  • src/main/runtime/anilist-setup.test.ts
  • src/main/runtime/anilist-setup.ts
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/main/runtime/app-lifecycle-actions.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/main/runtime/composers/anilist-setup-composer.test.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/main/runtime/mpv-main-event-actions.test.ts
  • src/main/runtime/mpv-main-event-actions.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/main/runtime/startup-config.test.ts
  • src/main/runtime/startup-config.ts
  • src/main/runtime/stats-server-routing.test.ts
  • src/main/runtime/stats-server-routing.ts
  • src/renderer/handlers/keyboard.test.ts
  • src/renderer/handlers/keyboard.ts
  • src/renderer/handlers/mouse.test.ts
  • src/renderer/handlers/mouse.ts
  • src/renderer/overlay-legacy-cleanup.test.ts
  • src/renderer/style.css
  • src/renderer/subtitle-render.test.ts
  • src/token-merger.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/window-trackers/hyprland-tracker.ts
  • stats/src/App.tsx
  • stats/src/styles/globals.css
  • stats/src/styles/globals.test.ts
💤 Files with no reviewable changes (1)
  • src/core/services/anilist/anilist-token-store.ts
✅ Files skipped from review due to trivial changes (18)
  • src/main/runtime/anilist-setup-protocol.test.ts
  • src/main/runtime/app-lifecycle-actions.ts
  • stats/src/styles/globals.test.ts
  • src/core/services/overlay-window-config.test.ts
  • src/main/runtime/stats-server-routing.ts
  • config.example.jsonc
  • src/main/runtime/startup-config.test.ts
  • src/core/services/anilist/anilist-updater.test.ts
  • src/config/definitions/defaults-core.ts
  • src/anki-integration/known-word-cache.test.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/main/runtime/stats-server-routing.test.ts
  • stats/src/App.tsx
  • src/core/services/mpv-properties.ts
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/ipc.test.ts
  • src/renderer/overlay-legacy-cleanup.test.ts
🚧 Files skipped from review as they are similar to previous changes (62)
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/main/runtime/anilist-setup-protocol-main-deps.test.ts
  • src/main/runtime/composers/anilist-setup-composer.test.ts
  • package.json
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/main/runtime/anilist-setup.ts
  • src/core/services/anilist/anilist-token-store.test.ts
  • src/core/services/overlay-visibility.test.ts
  • src/anki-integration.test.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/core/services/session-bindings.test.ts
  • plugin/subminer/session_bindings.lua
  • src/core/services/overlay-window-options.ts
  • src/main/dependencies.ts
  • src/anki-integration.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/renderer/handlers/keyboard.test.ts
  • src/anki-integration/card-creation-manual-update.test.ts
  • src/core/services/ipc.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/main/runtime/anilist-setup.test.ts
  • src/core/services/tokenizer/parser-selection-stage.test.ts
  • src/core/services/mpv.test.ts
  • src/anki-integration/card-creation.ts
  • src/core/services/stats-window.ts
  • src/core/services/overlay-visibility.ts
  • src/core/services/stats-window-runtime.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/anilist/anilist-updater.ts
  • src/core/services/mpv-protocol.ts
  • src/anki-integration/known-word-cache.ts
  • src/main/runtime/anilist-setup-protocol.ts
  • src/core/services/hyprland-window-placement.ts
  • src/main/runtime/startup-config.ts
  • src/core/services/tokenizer/grammar-ending.ts
  • src/core/services/hyprland-window-placement.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/renderer/style.css
  • scripts/test-plugin-session-bindings.lua
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/main/runtime/anilist-post-watch.test.ts
  • src/renderer/handlers/mouse.ts
  • src/main/runtime/mpv-main-event-actions.ts
  • src/window-trackers/hyprland-tracker.ts
  • src/token-merger.ts
  • plugin/subminer/process.lua
  • scripts/test-plugin-start-gate.lua
  • src/core/services/mpv.ts
  • src/main/runtime/mpv-main-event-actions.test.ts
  • src/config/definitions/domain-registry.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/stats-window.test.ts
  • src/renderer/handlers/keyboard.ts
  • src/core/services/overlay-window.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/main/runtime/anilist-post-watch.ts

ksyasuda added 2 commits May 11, 2026 01:07
- Add missing Lua CLI dispatch entry for openCharacterDictionary
- Add regression test for Alt+Meta+A binding and CLI flag forwarding
- Overlay no longer hides or becomes click-through during tracker refreshes when mpv is the focused window
- Preserve already-visible overlay when tracker is temporarily not ready but mpv target signal is active
- Add regression tests for active-mpv tracker refresh and transient tracker-not-ready paths

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
src/renderer/subtitle-render.test.ts (1)

1105-1110: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Include word-name-match in the JLPT lock coverage.

This loop still skips the name-match path, so a regression where word-name-match breaks the JLPT underline lock would pass unnoticed.

Suggested test update
     for (const annotationClass of [
       'word-known',
       'word-n-plus-one',
+      'word-name-match',
       'word-frequency-single',
       'word-frequency-band-2',
     ]) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/subtitle-render.test.ts` around lines 1105 - 1110, The test loop
over annotationClass (in subtitle-render.test.ts) omits the 'word-name-match'
case, so update the array literal used in the for (const annotationClass of
[...]) loop to include 'word-name-match' alongside 'word-known',
'word-n-plus-one', 'word-frequency-single', and 'word-frequency-band-2' so the
JLPT underline lock path for name matches is exercised by the test.
🧹 Nitpick comments (3)
src/main/runtime/app-lifecycle-actions.test.ts (1)

47-52: ⚡ Quick win

Assert ordering, not just presence, for the Linux timeout cleanup.

Line 51 currently checks inclusion only; the test can still pass if this call moves after window destruction.

🧪 Suggested assertion hardening
   assert.ok(calls.includes('clear-linux-mpv-fullscreen-overlay-refresh-timeouts'));
+  assert.ok(
+    calls.indexOf('clear-windows-visible-overlay-poll') <
+      calls.indexOf('clear-linux-mpv-fullscreen-overlay-refresh-timeouts'),
+  );
+  assert.ok(
+    calls.indexOf('clear-linux-mpv-fullscreen-overlay-refresh-timeouts') <
+      calls.indexOf('destroy-main-overlay-window'),
+  );
   assert.ok(calls.indexOf('flush-mpv-log') < calls.indexOf('destroy-socket'));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/runtime/app-lifecycle-actions.test.ts` around lines 47 - 52, The
test currently only checks that
'clear-linux-mpv-fullscreen-overlay-refresh-timeouts' is present; change that to
assert its position relative to the window-cleanup call so ordering is enforced.
Replace or augment the calls.includes check with an ordering assertion such as
asserting calls.indexOf('clear-linux-mpv-fullscreen-overlay-refresh-timeouts') <
calls.indexOf('clear-windows-visible-overlay-poll') so the Linux timeout cleanup
happens before the window-visible-overlay poll is cleared (use the 'calls' array
and the exact string symbols shown).
src/core/services/tokenizer/annotation-stage.test.ts (1)

262-1896: 🏗️ Heavy lift

Consider splitting this test module by concern before it grows further.

This PR adds substantial new coverage to an already very large TypeScript test file. Splitting by domain (e.g., subtitle exclusion rules, metadata stripping, N+1 behavior) would make failures easier to triage and keep maintenance manageable.

As per coding guidelines, "**/*.{ts,tsx}: TypeScript files should be kept small and follow existing patterns".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/services/tokenizer/annotation-stage.test.ts` around lines 262 -
1896, The test file has grown large and should be split by concern: move all
tests exercising subtitle-exclusion logic (those using
shouldExcludeTokenFromSubtitleAnnotations and
shouldExcludeTokenFromVocabularyPersistence) into a new
subtitle-exclusions.test.ts, move metadata tests
(stripSubtitleAnnotationMetadata) into metadata.test.ts, and move
N+1/frequency/JLPT behavior tests (annotateTokens and related helpers) into
nplusone-annotations.test.ts; ensure each new file imports the shared helpers
(makeToken, makeDeps, PartOfSpeech, and the functions under test) from the
original module, preserve all test names and assertions, and update any
index/test-glob patterns if necessary so the test runner still picks them up.
src/core/services/stats-window.ts (1)

49-56: ⚡ Quick win

Re-resolve bounds for each placement retry to avoid stale geometry.

showStatsWindow uses a single bounds snapshot from Line 49 for all later retries. If tracked overlay bounds change during show/placement, retries can apply outdated values.

♻️ Proposed fix
 function showStatsWindow(window: BrowserWindow, options: StatsWindowOptions): void {
-  const bounds = options.resolveBounds();
-  let placementBounds = syncStatsWindowBounds(window, bounds);
+  let placementBounds = syncStatsWindowBounds(window, options.resolveBounds());
   promoteStatsWindowLevel(window);
   window.show();
-  placementBounds = syncStatsWindowBounds(window, bounds) ?? placementBounds;
+  placementBounds = syncStatsWindowBounds(window, options.resolveBounds()) ?? placementBounds;
   if (!ensureHyprlandWindowFloatingByTitle({ title: STATS_WINDOW_TITLE, bounds: placementBounds })) {
-    placementBounds = syncStatsWindowBounds(window, bounds) ?? placementBounds;
+    placementBounds = syncStatsWindowBounds(window, options.resolveBounds()) ?? placementBounds;
   }
   window.focus();
   options.onVisibilityChanged?.(true);
   promoteStatsWindowLevel(window);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/core/services/stats-window.ts` around lines 49 - 56, The code captures a
single bounds snapshot (const bounds = options.resolveBounds()) and reuses it
for all placement attempts, which can apply stale geometry; update
show/placement logic so you re-resolve bounds for each placement call—replace
uses of the captured bounds in syncStatsWindowBounds(window, bounds) with fresh
calls to options.resolveBounds() before each sync (including the initial
placement and the retry after ensureHyprlandWindowFloatingByTitle), and remove
or stop relying on the single `bounds` variable so each syncStatsWindowBounds
and subsequent ensureHyprlandWindowFloatingByTitle call uses up-to-date
geometry.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/renderer/subtitle-render.test.ts`:
- Around line 1105-1110: The test loop over annotationClass (in
subtitle-render.test.ts) omits the 'word-name-match' case, so update the array
literal used in the for (const annotationClass of [...]) loop to include
'word-name-match' alongside 'word-known', 'word-n-plus-one',
'word-frequency-single', and 'word-frequency-band-2' so the JLPT underline lock
path for name matches is exercised by the test.

---

Nitpick comments:
In `@src/core/services/stats-window.ts`:
- Around line 49-56: The code captures a single bounds snapshot (const bounds =
options.resolveBounds()) and reuses it for all placement attempts, which can
apply stale geometry; update show/placement logic so you re-resolve bounds for
each placement call—replace uses of the captured bounds in
syncStatsWindowBounds(window, bounds) with fresh calls to
options.resolveBounds() before each sync (including the initial placement and
the retry after ensureHyprlandWindowFloatingByTitle), and remove or stop relying
on the single `bounds` variable so each syncStatsWindowBounds and subsequent
ensureHyprlandWindowFloatingByTitle call uses up-to-date geometry.

In `@src/core/services/tokenizer/annotation-stage.test.ts`:
- Around line 262-1896: The test file has grown large and should be split by
concern: move all tests exercising subtitle-exclusion logic (those using
shouldExcludeTokenFromSubtitleAnnotations and
shouldExcludeTokenFromVocabularyPersistence) into a new
subtitle-exclusions.test.ts, move metadata tests
(stripSubtitleAnnotationMetadata) into metadata.test.ts, and move
N+1/frequency/JLPT behavior tests (annotateTokens and related helpers) into
nplusone-annotations.test.ts; ensure each new file imports the shared helpers
(makeToken, makeDeps, PartOfSpeech, and the functions under test) from the
original module, preserve all test names and assertions, and update any
index/test-glob patterns if necessary so the test runner still picks them up.

In `@src/main/runtime/app-lifecycle-actions.test.ts`:
- Around line 47-52: The test currently only checks that
'clear-linux-mpv-fullscreen-overlay-refresh-timeouts' is present; change that to
assert its position relative to the window-cleanup call so ordering is enforced.
Replace or augment the calls.includes check with an ordering assertion such as
asserting calls.indexOf('clear-linux-mpv-fullscreen-overlay-refresh-timeouts') <
calls.indexOf('clear-windows-visible-overlay-poll') so the Linux timeout cleanup
happens before the window-visible-overlay poll is cleared (use the 'calls' array
and the exact string symbols shown).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a1ee604c-b105-4b48-baf9-ecbae0e62739

📥 Commits

Reviewing files that changed from the base of the PR and between 4cb7b53 and eff33e2.

⛔ Files ignored due to path filters (72)
  • backlog/completed/task-336 - Fix-Hyprland-fullscreen-overlay-downward-offset.md is excluded by !backlog/**
  • backlog/completed/task-339 - Stop-pinning-Hyprland-overlay-windows.md is excluded by !backlog/**
  • backlog/tasks/task-304 - Fix-N1-sentence-boundary-counting-across-Yomitan-punctuation-gaps.md is excluded by !backlog/**
  • backlog/tasks/task-305 - Use-Yomitan-word-classes-for-subtitle-token-POS-filtering.md is excluded by !backlog/**
  • backlog/tasks/task-306 - Fix-Hyprland-fullscreen-overlay-geometry-and-hover-pause.md is excluded by !backlog/**
  • backlog/tasks/task-307 - Exclude-kana-only-words-from-N1-subtitle-targets.md is excluded by !backlog/**
  • backlog/tasks/task-308 - Restore-persistent-JLPT-subtitle-underlines.md is excluded by !backlog/**
  • backlog/tasks/task-309 - Accept-modified-follow-up-digits-for-multi-line-sentence-mining.md is excluded by !backlog/**
  • backlog/tasks/task-310 - Suppress-N1-highlight-for-kana-only-candidate-sentences.md is excluded by !backlog/**
  • backlog/tasks/task-311 - Suppress-auxiliary-inflection-fragments-from-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-312 - Suppress-ja-nai-explanatory-ending-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-315 - Suppress-annotations-for-standalone-じゃない-and-です-ending-tokens.md is excluded by !backlog/**
  • backlog/tasks/task-316 - Fix-macOS-launcher-playback-exit-with-background-stats-daemon.md is excluded by !backlog/**
  • backlog/tasks/task-318 - Keep-JLPT-underline-color-fixed-after-lookup-selection.md is excluded by !backlog/**
  • backlog/tasks/task-319 - Suppress-annotations-for-expressive-interjection-subtitles.md is excluded by !backlog/**
  • backlog/tasks/task-320 - Refresh-current-subtitle-known-word-highlight-after-successful-mining.md is excluded by !backlog/**
  • backlog/tasks/task-321 - Preserve-word-audio-during-manual-clipboard-card-updates.md is excluded by !backlog/**
  • backlog/tasks/task-322 - Fix-failing-CI-checks-on-PR-57.md is excluded by !backlog/**
  • backlog/tasks/task-323 - Fix-macOS-overlay-hiding-while-mpv-remains-active.md is excluded by !backlog/**
  • backlog/tasks/task-324 - Fix-mpv-playlist-changes-re-running-app-warmups.md is excluded by !backlog/**
  • backlog/tasks/task-325 - Keep-JLPT-underline-color-fixed-with-combined-lookup-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-326 - Fix-AniList-post-watch-update-after-skipped-completion-threshold.md is excluded by !backlog/**
  • backlog/tasks/task-327 - Restore-stats-daemon-deferral-when-launching-playback.md is excluded by !backlog/**
  • backlog/tasks/task-328 - Keep-subtitle-prefetch-running-after-immediate-cached-annotation-render.md is excluded by !backlog/**
  • backlog/tasks/task-331 - Address-unresolved-CodeRabbit-comments-on-PR-57.md is excluded by !backlog/**
  • backlog/tasks/task-332 - Fix-subtitle-frequency-annotation-missing-ranks-shown-in-Yomitan-popup.md is excluded by !backlog/**
  • backlog/tasks/task-333 - Suppress-aru-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-334 - Assess-and-address-PR-57-latest-CodeRabbit-comments.md is excluded by !backlog/**
  • backlog/tasks/task-335 - Fix-Linux-AniList-setup-gate-using-stored-keyring-token.md is excluded by !backlog/**
  • backlog/tasks/task-337 - Fix-transient-Linux-safeStorage-failure-poisoning-AniList-token-store.md is excluded by !backlog/**
  • backlog/tasks/task-338 - Fix-known-word-highlight-on-standalone-subtitle-particles.md is excluded by !backlog/**
  • backlog/tasks/task-340 - Restore-default-replay-and-next-subtitle-overlay-keybindings.md is excluded by !backlog/**
  • backlog/tasks/task-341 - Fix-frequency-highlight-for-honorific-prefix-noun-tokens.md is excluded by !backlog/**
  • backlog/tasks/task-343 - Fix-macOS-character-dictionary-selector-session-shortcut.md is excluded by !backlog/**
  • backlog/tasks/task-344 - Fix-macOS-overlay-tracker-hiding-while-mpv-remains-active.md is excluded by !backlog/**
  • changes/305-tokenizer-word-class-pos-filtering.md is excluded by !changes/**
  • changes/306-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/307-kana-nplusone-targets.md is excluded by !changes/**
  • changes/308-jlpt-underlines.md is excluded by !changes/**
  • changes/309-multi-mine-modified-digits.md is excluded by !changes/**
  • changes/311-auxiliary-inflection-annotation-filter.md is excluded by !changes/**
  • changes/312-grammar-ending-annotation-filter.md is excluded by !changes/**
  • changes/316-macos-playback-stats-daemon.md is excluded by !changes/**
  • changes/318-jlpt-underline-selection.md is excluded by !changes/**
  • changes/319-interjection-annotation-filter.md is excluded by !changes/**
  • changes/320-current-subtitle-known-highlight.md is excluded by !changes/**
  • changes/321-grammar-ending-pattern-filter.md is excluded by !changes/**
  • changes/322-preserve-word-audio-manual-update.md is excluded by !changes/**
  • changes/323-macos-overlay-tracker-flaps.md is excluded by !changes/**
  • changes/324-mpv-playlist-overlay-reuse.md is excluded by !changes/**
  • changes/325-jlpt-combined-annotation-underlines.md is excluded by !changes/**
  • changes/326-anilist-time-position-post-watch.md is excluded by !changes/**
  • changes/327-stats-daemon-deferral.md is excluded by !changes/**
  • changes/328-subtitle-prefetch-cache-hit-resume.md is excluded by !changes/**
  • changes/332-subtitle-frequency-ordinal-prefix.md is excluded by !changes/**
  • changes/333-aru-annotation-filter.md is excluded by !changes/**
  • changes/334-coderabbit-followups.md is excluded by !changes/**
  • changes/335-anilist-linux-token-setup.md is excluded by !changes/**
  • changes/336-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/336-overlay-focus-ring.md is excluded by !changes/**
  • changes/337-anilist-safe-storage-retry.md is excluded by !changes/**
  • changes/338-known-word-particle-highlights.md is excluded by !changes/**
  • changes/339-hyprland-overlay-pin.md is excluded by !changes/**
  • changes/340-default-subtitle-keybindings.md is excluded by !changes/**
  • changes/344-macos-overlay-active-mpv.md is excluded by !changes/**
  • docs-site/anki-integration.md is excluded by !docs-site/**
  • docs-site/configuration.md is excluded by !docs-site/**
  • docs-site/mining-workflow.md is excluded by !docs-site/**
  • docs-site/public/config.example.jsonc is excluded by !docs-site/**
  • docs-site/shortcuts.md is excluded by !docs-site/**
  • docs-site/troubleshooting.md is excluded by !docs-site/**
  • docs-site/usage.md is excluded by !docs-site/**
📒 Files selected for processing (91)
  • config.example.jsonc
  • package.json
  • plugin/subminer/lifecycle.lua
  • plugin/subminer/process.lua
  • plugin/subminer/session_bindings.lua
  • scripts/test-plugin-session-bindings.lua
  • scripts/test-plugin-start-gate.lua
  • src/anki-integration.test.ts
  • src/anki-integration.ts
  • src/anki-integration/card-creation-manual-update.test.ts
  • src/anki-integration/card-creation.ts
  • src/anki-integration/known-word-cache.test.ts
  • src/anki-integration/known-word-cache.ts
  • src/config/definitions/defaults-core.ts
  • src/config/definitions/domain-registry.test.ts
  • src/core/services/anilist/anilist-token-store.test.ts
  • src/core/services/anilist/anilist-token-store.ts
  • src/core/services/anilist/anilist-updater.test.ts
  • src/core/services/anilist/anilist-updater.ts
  • src/core/services/hyprland-window-placement.test.ts
  • src/core/services/hyprland-window-placement.ts
  • src/core/services/ipc.test.ts
  • src/core/services/ipc.ts
  • src/core/services/mpv-properties.ts
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/mpv-protocol.ts
  • src/core/services/mpv.test.ts
  • src/core/services/mpv.ts
  • src/core/services/overlay-manager.test.ts
  • src/core/services/overlay-visibility.test.ts
  • src/core/services/overlay-visibility.ts
  • src/core/services/overlay-window-config.test.ts
  • src/core/services/overlay-window-options.ts
  • src/core/services/overlay-window.ts
  • src/core/services/session-bindings.test.ts
  • src/core/services/stats-window-runtime.ts
  • src/core/services/stats-window.test.ts
  • src/core/services/stats-window.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/grammar-ending.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/tokenizer/parser-selection-stage.test.ts
  • src/core/services/tokenizer/parser-selection-stage.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/main.ts
  • src/main/dependencies.ts
  • src/main/runtime/anilist-post-watch.test.ts
  • src/main/runtime/anilist-post-watch.ts
  • src/main/runtime/anilist-setup-protocol-main-deps.test.ts
  • src/main/runtime/anilist-setup-protocol.test.ts
  • src/main/runtime/anilist-setup-protocol.ts
  • src/main/runtime/anilist-setup.test.ts
  • src/main/runtime/anilist-setup.ts
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/main/runtime/app-lifecycle-actions.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/main/runtime/composers/anilist-setup-composer.test.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/main/runtime/mpv-main-event-actions.test.ts
  • src/main/runtime/mpv-main-event-actions.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/main/runtime/startup-config.test.ts
  • src/main/runtime/startup-config.ts
  • src/main/runtime/stats-server-routing.test.ts
  • src/main/runtime/stats-server-routing.ts
  • src/renderer/handlers/keyboard.test.ts
  • src/renderer/handlers/keyboard.ts
  • src/renderer/handlers/mouse.test.ts
  • src/renderer/handlers/mouse.ts
  • src/renderer/overlay-legacy-cleanup.test.ts
  • src/renderer/style.css
  • src/renderer/subtitle-render.test.ts
  • src/token-merger.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/window-trackers/hyprland-tracker.ts
  • stats/src/App.tsx
  • stats/src/styles/globals.css
  • stats/src/styles/globals.test.ts
💤 Files with no reviewable changes (1)
  • src/core/services/anilist/anilist-token-store.ts
✅ Files skipped from review due to trivial changes (19)
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/core/services/overlay-window-config.test.ts
  • src/main/runtime/composers/anilist-setup-composer.test.ts
  • src/main/runtime/anilist-setup-protocol.test.ts
  • src/config/definitions/defaults-core.ts
  • config.example.jsonc
  • src/main/runtime/anilist-setup.test.ts
  • src/main/runtime/startup-config.test.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/core/services/anilist/anilist-updater.test.ts
  • plugin/subminer/process.lua
  • src/main/runtime/stats-server-routing.test.ts
  • stats/src/styles/globals.css
  • src/core/services/mpv-protocol.test.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/core/services/stats-window.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/renderer/handlers/keyboard.test.ts
🚧 Files skipped from review as they are similar to previous changes (51)
  • stats/src/styles/globals.test.ts
  • src/config/definitions/domain-registry.test.ts
  • src/main/runtime/stats-server-routing.ts
  • src/anki-integration.ts
  • src/core/services/overlay-window-options.ts
  • src/renderer/overlay-legacy-cleanup.test.ts
  • stats/src/App.tsx
  • src/anki-integration/card-creation-manual-update.test.ts
  • src/main/runtime/startup-config.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/dependencies.ts
  • src/core/services/session-bindings.test.ts
  • src/core/services/mpv-protocol.ts
  • src/core/services/anilist/anilist-updater.ts
  • src/main/runtime/anilist-setup-protocol-main-deps.test.ts
  • src/core/services/tokenizer/parser-selection-stage.test.ts
  • src/renderer/handlers/mouse.ts
  • src/core/services/mpv.ts
  • src/renderer/handlers/keyboard.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • plugin/subminer/lifecycle.lua
  • src/core/services/ipc.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/core/services/tokenizer/parser-selection-stage.ts
  • src/core/services/overlay-window.ts
  • src/anki-integration/known-word-cache.ts
  • plugin/subminer/session_bindings.lua
  • src/core/services/ipc.test.ts
  • src/core/services/mpv-properties.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/tokenizer.ts
  • src/main/runtime/mpv-main-event-actions.test.ts
  • src/anki-integration.test.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/stats-window-runtime.ts
  • src/main/runtime/anilist-setup.ts
  • src/core/services/hyprland-window-placement.ts
  • src/token-merger.ts
  • src/main.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/anilist-post-watch.ts
  • src/main/runtime/anilist-post-watch.test.ts
  • src/window-trackers/hyprland-tracker.ts
  • scripts/test-plugin-session-bindings.lua
  • src/core/services/tokenizer/annotation-stage.ts
  • src/renderer/style.css
  • src/core/services/overlay-manager.test.ts
  • src/main/runtime/anilist-setup-protocol.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer/grammar-ending.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
src/renderer/style.css (1)

43-47: 💤 Low value

Consider using :focus-visible instead of :focus for accessibility.

Using :focus removes focus outlines for all focus states, including keyboard navigation. The :focus-visible pseudo-class would preserve focus indicators for keyboard users while still removing them for mouse/touch interactions.

Since this is a media player overlay, the accessibility requirements may differ, but :focus-visible is generally the more accessible approach.

Suggested change
-html:focus,
-body:focus,
-#overlay:focus {
+html:focus-visible,
+body:focus-visible,
+#overlay:focus-visible {
   outline: none;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/style.css` around lines 43 - 47, The CSS rule targeting
html:focus, body:focus, and `#overlay`:focus is removing focus outlines for all
focus types; update these selectors to html:focus-visible, body:focus-visible,
and `#overlay`:focus-visible so keyboard focus indicators are preserved while
still hiding outlines for mouse/touch interactions, leaving the declaration
block (outline: none) unchanged for those :focus-visible selectors where
appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/services/mpv.ts`:
- Around line 528-530: In playNextSubtitle, the current check only unpauses when
this.playbackPaused === true which leaves the player paused if playbackPaused is
null during startup/reconnect; change the logic in playNextSubtitle to
unconditionally clear pause when the player might be paused by checking for
truthy (true) OR unknown/null startup state (e.g., treat null as paused) and
call this.send({ command: ['set_property', 'pause', false] }) when
playbackPaused is not explicitly false so that sub-seek does not run while still
paused; update any state transitions that set this.playbackPaused to ensure they
reflect the true player state after the unpause command.

In `@src/core/services/tokenizer/parser-selection-stage.ts`:
- Around line 273-275: The slice call on combinedReading incorrectly uses
-segment.reading.length which becomes -0 for an empty string and clears
combinedReading; update the logic in the parser-selection-stage where
segment.reading and combinedReading are handled so you only perform the slice
when segment.reading is a non-empty string (e.g., check segment.reading.length >
0) or compute the end index as combinedReading.length - segment.reading.length
to avoid using -0; ensure this change is applied around the existing if (typeof
segment.reading === 'string') block so segment.reading === "" no longer wipes
combinedReading.

---

Nitpick comments:
In `@src/renderer/style.css`:
- Around line 43-47: The CSS rule targeting html:focus, body:focus, and
`#overlay`:focus is removing focus outlines for all focus types; update these
selectors to html:focus-visible, body:focus-visible, and `#overlay`:focus-visible
so keyboard focus indicators are preserved while still hiding outlines for
mouse/touch interactions, leaving the declaration block (outline: none)
unchanged for those :focus-visible selectors where appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: e28b3212-6b15-43a9-956e-41f3998aaca1

📥 Commits

Reviewing files that changed from the base of the PR and between 4cb7b53 and eff33e2.

⛔ Files ignored due to path filters (72)
  • backlog/completed/task-336 - Fix-Hyprland-fullscreen-overlay-downward-offset.md is excluded by !backlog/**
  • backlog/completed/task-339 - Stop-pinning-Hyprland-overlay-windows.md is excluded by !backlog/**
  • backlog/tasks/task-304 - Fix-N1-sentence-boundary-counting-across-Yomitan-punctuation-gaps.md is excluded by !backlog/**
  • backlog/tasks/task-305 - Use-Yomitan-word-classes-for-subtitle-token-POS-filtering.md is excluded by !backlog/**
  • backlog/tasks/task-306 - Fix-Hyprland-fullscreen-overlay-geometry-and-hover-pause.md is excluded by !backlog/**
  • backlog/tasks/task-307 - Exclude-kana-only-words-from-N1-subtitle-targets.md is excluded by !backlog/**
  • backlog/tasks/task-308 - Restore-persistent-JLPT-subtitle-underlines.md is excluded by !backlog/**
  • backlog/tasks/task-309 - Accept-modified-follow-up-digits-for-multi-line-sentence-mining.md is excluded by !backlog/**
  • backlog/tasks/task-310 - Suppress-N1-highlight-for-kana-only-candidate-sentences.md is excluded by !backlog/**
  • backlog/tasks/task-311 - Suppress-auxiliary-inflection-fragments-from-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-312 - Suppress-ja-nai-explanatory-ending-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-315 - Suppress-annotations-for-standalone-じゃない-and-です-ending-tokens.md is excluded by !backlog/**
  • backlog/tasks/task-316 - Fix-macOS-launcher-playback-exit-with-background-stats-daemon.md is excluded by !backlog/**
  • backlog/tasks/task-318 - Keep-JLPT-underline-color-fixed-after-lookup-selection.md is excluded by !backlog/**
  • backlog/tasks/task-319 - Suppress-annotations-for-expressive-interjection-subtitles.md is excluded by !backlog/**
  • backlog/tasks/task-320 - Refresh-current-subtitle-known-word-highlight-after-successful-mining.md is excluded by !backlog/**
  • backlog/tasks/task-321 - Preserve-word-audio-during-manual-clipboard-card-updates.md is excluded by !backlog/**
  • backlog/tasks/task-322 - Fix-failing-CI-checks-on-PR-57.md is excluded by !backlog/**
  • backlog/tasks/task-323 - Fix-macOS-overlay-hiding-while-mpv-remains-active.md is excluded by !backlog/**
  • backlog/tasks/task-324 - Fix-mpv-playlist-changes-re-running-app-warmups.md is excluded by !backlog/**
  • backlog/tasks/task-325 - Keep-JLPT-underline-color-fixed-with-combined-lookup-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-326 - Fix-AniList-post-watch-update-after-skipped-completion-threshold.md is excluded by !backlog/**
  • backlog/tasks/task-327 - Restore-stats-daemon-deferral-when-launching-playback.md is excluded by !backlog/**
  • backlog/tasks/task-328 - Keep-subtitle-prefetch-running-after-immediate-cached-annotation-render.md is excluded by !backlog/**
  • backlog/tasks/task-331 - Address-unresolved-CodeRabbit-comments-on-PR-57.md is excluded by !backlog/**
  • backlog/tasks/task-332 - Fix-subtitle-frequency-annotation-missing-ranks-shown-in-Yomitan-popup.md is excluded by !backlog/**
  • backlog/tasks/task-333 - Suppress-aru-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-334 - Assess-and-address-PR-57-latest-CodeRabbit-comments.md is excluded by !backlog/**
  • backlog/tasks/task-335 - Fix-Linux-AniList-setup-gate-using-stored-keyring-token.md is excluded by !backlog/**
  • backlog/tasks/task-337 - Fix-transient-Linux-safeStorage-failure-poisoning-AniList-token-store.md is excluded by !backlog/**
  • backlog/tasks/task-338 - Fix-known-word-highlight-on-standalone-subtitle-particles.md is excluded by !backlog/**
  • backlog/tasks/task-340 - Restore-default-replay-and-next-subtitle-overlay-keybindings.md is excluded by !backlog/**
  • backlog/tasks/task-341 - Fix-frequency-highlight-for-honorific-prefix-noun-tokens.md is excluded by !backlog/**
  • backlog/tasks/task-343 - Fix-macOS-character-dictionary-selector-session-shortcut.md is excluded by !backlog/**
  • backlog/tasks/task-344 - Fix-macOS-overlay-tracker-hiding-while-mpv-remains-active.md is excluded by !backlog/**
  • changes/305-tokenizer-word-class-pos-filtering.md is excluded by !changes/**
  • changes/306-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/307-kana-nplusone-targets.md is excluded by !changes/**
  • changes/308-jlpt-underlines.md is excluded by !changes/**
  • changes/309-multi-mine-modified-digits.md is excluded by !changes/**
  • changes/311-auxiliary-inflection-annotation-filter.md is excluded by !changes/**
  • changes/312-grammar-ending-annotation-filter.md is excluded by !changes/**
  • changes/316-macos-playback-stats-daemon.md is excluded by !changes/**
  • changes/318-jlpt-underline-selection.md is excluded by !changes/**
  • changes/319-interjection-annotation-filter.md is excluded by !changes/**
  • changes/320-current-subtitle-known-highlight.md is excluded by !changes/**
  • changes/321-grammar-ending-pattern-filter.md is excluded by !changes/**
  • changes/322-preserve-word-audio-manual-update.md is excluded by !changes/**
  • changes/323-macos-overlay-tracker-flaps.md is excluded by !changes/**
  • changes/324-mpv-playlist-overlay-reuse.md is excluded by !changes/**
  • changes/325-jlpt-combined-annotation-underlines.md is excluded by !changes/**
  • changes/326-anilist-time-position-post-watch.md is excluded by !changes/**
  • changes/327-stats-daemon-deferral.md is excluded by !changes/**
  • changes/328-subtitle-prefetch-cache-hit-resume.md is excluded by !changes/**
  • changes/332-subtitle-frequency-ordinal-prefix.md is excluded by !changes/**
  • changes/333-aru-annotation-filter.md is excluded by !changes/**
  • changes/334-coderabbit-followups.md is excluded by !changes/**
  • changes/335-anilist-linux-token-setup.md is excluded by !changes/**
  • changes/336-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/336-overlay-focus-ring.md is excluded by !changes/**
  • changes/337-anilist-safe-storage-retry.md is excluded by !changes/**
  • changes/338-known-word-particle-highlights.md is excluded by !changes/**
  • changes/339-hyprland-overlay-pin.md is excluded by !changes/**
  • changes/340-default-subtitle-keybindings.md is excluded by !changes/**
  • changes/344-macos-overlay-active-mpv.md is excluded by !changes/**
  • docs-site/anki-integration.md is excluded by !docs-site/**
  • docs-site/configuration.md is excluded by !docs-site/**
  • docs-site/mining-workflow.md is excluded by !docs-site/**
  • docs-site/public/config.example.jsonc is excluded by !docs-site/**
  • docs-site/shortcuts.md is excluded by !docs-site/**
  • docs-site/troubleshooting.md is excluded by !docs-site/**
  • docs-site/usage.md is excluded by !docs-site/**
📒 Files selected for processing (91)
  • config.example.jsonc
  • package.json
  • plugin/subminer/lifecycle.lua
  • plugin/subminer/process.lua
  • plugin/subminer/session_bindings.lua
  • scripts/test-plugin-session-bindings.lua
  • scripts/test-plugin-start-gate.lua
  • src/anki-integration.test.ts
  • src/anki-integration.ts
  • src/anki-integration/card-creation-manual-update.test.ts
  • src/anki-integration/card-creation.ts
  • src/anki-integration/known-word-cache.test.ts
  • src/anki-integration/known-word-cache.ts
  • src/config/definitions/defaults-core.ts
  • src/config/definitions/domain-registry.test.ts
  • src/core/services/anilist/anilist-token-store.test.ts
  • src/core/services/anilist/anilist-token-store.ts
  • src/core/services/anilist/anilist-updater.test.ts
  • src/core/services/anilist/anilist-updater.ts
  • src/core/services/hyprland-window-placement.test.ts
  • src/core/services/hyprland-window-placement.ts
  • src/core/services/ipc.test.ts
  • src/core/services/ipc.ts
  • src/core/services/mpv-properties.ts
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/mpv-protocol.ts
  • src/core/services/mpv.test.ts
  • src/core/services/mpv.ts
  • src/core/services/overlay-manager.test.ts
  • src/core/services/overlay-visibility.test.ts
  • src/core/services/overlay-visibility.ts
  • src/core/services/overlay-window-config.test.ts
  • src/core/services/overlay-window-options.ts
  • src/core/services/overlay-window.ts
  • src/core/services/session-bindings.test.ts
  • src/core/services/stats-window-runtime.ts
  • src/core/services/stats-window.test.ts
  • src/core/services/stats-window.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/grammar-ending.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/tokenizer/parser-selection-stage.test.ts
  • src/core/services/tokenizer/parser-selection-stage.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/main.ts
  • src/main/dependencies.ts
  • src/main/runtime/anilist-post-watch.test.ts
  • src/main/runtime/anilist-post-watch.ts
  • src/main/runtime/anilist-setup-protocol-main-deps.test.ts
  • src/main/runtime/anilist-setup-protocol.test.ts
  • src/main/runtime/anilist-setup-protocol.ts
  • src/main/runtime/anilist-setup.test.ts
  • src/main/runtime/anilist-setup.ts
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/main/runtime/app-lifecycle-actions.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/main/runtime/composers/anilist-setup-composer.test.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/main/runtime/mpv-main-event-actions.test.ts
  • src/main/runtime/mpv-main-event-actions.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/main/runtime/startup-config.test.ts
  • src/main/runtime/startup-config.ts
  • src/main/runtime/stats-server-routing.test.ts
  • src/main/runtime/stats-server-routing.ts
  • src/renderer/handlers/keyboard.test.ts
  • src/renderer/handlers/keyboard.ts
  • src/renderer/handlers/mouse.test.ts
  • src/renderer/handlers/mouse.ts
  • src/renderer/overlay-legacy-cleanup.test.ts
  • src/renderer/style.css
  • src/renderer/subtitle-render.test.ts
  • src/token-merger.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/window-trackers/hyprland-tracker.ts
  • stats/src/App.tsx
  • stats/src/styles/globals.css
  • stats/src/styles/globals.test.ts
💤 Files with no reviewable changes (1)
  • src/core/services/anilist/anilist-token-store.ts
✅ Files skipped from review due to trivial changes (20)
  • config.example.jsonc
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • stats/src/App.tsx
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/main/runtime/startup-config.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/main/runtime/anilist-setup-protocol.test.ts
  • stats/src/styles/globals.test.ts
  • src/main/runtime/startup-config.test.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/core/services/overlay-window-options.ts
  • src/renderer/overlay-legacy-cleanup.test.ts
  • src/anki-integration/card-creation-manual-update.test.ts
  • src/main/runtime/stats-server-routing.test.ts
  • src/config/definitions/defaults-core.ts
  • src/main/runtime/anilist-setup-protocol.ts
  • src/main/runtime/anilist-setup-protocol-main-deps.test.ts
  • src/main/runtime/composers/anilist-setup-composer.test.ts
  • src/anki-integration/known-word-cache.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
🚧 Files skipped from review as they are similar to previous changes (53)
  • src/core/services/overlay-window-config.test.ts
  • src/main/runtime/anilist-setup.ts
  • src/main/runtime/stats-server-routing.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/dependencies.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/core/services/overlay-manager.test.ts
  • src/core/services/session-bindings.test.ts
  • src/core/services/mpv-protocol.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/core/services/ipc.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/core/services/anilist/anilist-updater.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • scripts/test-plugin-session-bindings.lua
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • package.json
  • src/anki-integration.ts
  • stats/src/styles/globals.css
  • src/renderer/handlers/keyboard.ts
  • src/anki-integration/card-creation.ts
  • src/core/services/anilist/anilist-updater.ts
  • src/renderer/handlers/mouse.test.ts
  • scripts/test-plugin-start-gate.lua
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/core/services/tokenizer/parser-selection-stage.test.ts
  • plugin/subminer/lifecycle.lua
  • src/core/services/tokenizer.ts
  • src/anki-integration/known-word-cache.ts
  • src/main/runtime/mpv-main-event-actions.ts
  • src/main/runtime/anilist-post-watch.test.ts
  • src/main/runtime/app-lifecycle-actions.ts
  • src/core/services/tokenizer/grammar-ending.ts
  • src/core/services/anilist/anilist-token-store.test.ts
  • src/core/services/ipc.test.ts
  • src/core/services/stats-window.test.ts
  • plugin/subminer/process.lua
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/overlay-window.ts
  • src/renderer/handlers/mouse.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/core/services/stats-window-runtime.ts
  • src/window-trackers/hyprland-tracker.ts
  • src/core/services/hyprland-window-placement.test.ts
  • src/core/services/hyprland-window-placement.ts
  • src/main/runtime/mpv-main-event-actions.test.ts
  • src/main/runtime/anilist-setup.test.ts
  • src/core/services/tokenizer.test.ts
  • src/main.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/token-merger.ts
  • src/core/services/stats-window.ts

Comment thread src/core/services/mpv.ts Outdated
Comment thread src/core/services/tokenizer/parser-selection-stage.ts Outdated
'お前',
'こいつ',
'こっち',
'くれ',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don't blacklist bare くれ via the global term set.

Line 35 adds 'くれ' to the broad blacklist, and Lines 496-498 only rescue the くれる verb case. isExcludedByTerm() still matches surface/reading/headword, so this also strips lexical content tokens like surface='くれ', headword='暮れ', pos1='名詞', pos2='一般'. Please move bare-くれ filtering into a POS-aware helper instead of the shared term set.

Also applies to: 496-498

ksyasuda added 2 commits May 11, 2026 23:48
- Change `getMediaDetail` JOIN to LEFT JOIN on `imm_lifetime_media` and fall back to aggregated session metrics when no lifetime row exists
- Add filter `AND (lm.video_id IS NOT NULL OR s.session_id IS NOT NULL)` to keep results valid
- Add regression test covering the session-visible / media-detail-missing mismatch

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
src/renderer/subtitle-render.test.ts (1)

1105-1110: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Cover the word-name-match JLPT lock path.

Line 1105 still skips word-name-match, so a regression where name-match tokens lose their JLPT underline lock would pass unnoticed.

Suggested coverage addition
     for (const annotationClass of [
       'word-known',
       'word-n-plus-one',
+      'word-name-match',
       'word-frequency-single',
       'word-frequency-band-2',
     ]) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/renderer/subtitle-render.test.ts` around lines 1105 - 1110, The test loop
that iterates annotationClass in subtitle-render.test.ts omits the
'word-name-match' case, so add 'word-name-match' to the array of classes used in
that for loop (the array initialized where annotationClass is iterated) so the
JLPT underline lock path for name-match tokens is exercised; update any related
assertions in the same loop to expect the same JLPT-lock behavior for
'word-name-match' as for the other word-* classes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/services/immersion-tracker/query-library.ts`:
- Around line 266-268: The ELSE branch that currently uses
COALESCE(SUM(COALESCE(asm.tokensSeen, s.tokens_seen, 0)), 0) must use the same
filtered/session_word_counts aggregation used elsewhere instead of raw
tokens_seen to avoid overcounting; replace that SUM with the SUM over the
session_word_counts-derived field (the same alias used to compute
asm.tokensSeen, e.g., SUM(awc.filtered_tokens) or the exact column name produced
by the session_word_counts subquery) so totalTokensSeen matches
getMediaSessions/getAnimeEpisodes rollups, and apply the identical change for
the second occurrence referenced at lines 291-292.

In `@src/main.ts`:
- Around line 3862-3872: The onFullscreenChange handler is ignoring the
fullscreen payload and always starts a refresh burst; change it to read the
payload.fullscreen boolean and only call
scheduleLinuxVisibleOverlayFullscreenRefreshBurst when fullscreen === true
(using the same overlayManager, overlayVisibilityRuntime, and
ensureOverlayWindowLevel arguments), and when fullscreen === false clear/cancel
any existing burst by invoking cancelLinuxMpvFullscreenOverlayRefreshBurst
(instead of rescheduling). Ensure cancelLinuxMpvFullscreenOverlayRefreshBurst is
updated/used to null out the stored timer so repeated exits don't replay the
workaround.

In `@src/window-trackers/hyprland-tracker.ts`:
- Around line 189-208: The isHyprlandGeometryEvent function currently treats
'windowtitle' and 'windowtitlev2' as geometry events causing geometry-poll
bursts; remove those two string checks from the boolean expression in
isHyprlandGeometryEvent so title-only events no longer trigger the 4-call poll
burst (inspect the function name isHyprlandGeometryEvent to locate the condition
and delete the 'windowtitle' and 'windowtitlev2' entries).
- Around line 147-159: parseHyprctlMonitors currently calls JSON.parse directly
and can throw on malformed JSON; update it to catch parse errors and return null
on failure to honor the HyprlandMonitor[] | null contract. Wrap the
JSON.parse(jsonPayload) call in a try/catch (inside parseHyprctlMonitors), and
if parsing throws or results in a non-array, return null; keep using
extractHyprctlJsonPayload to get the payload and then cast the successful parsed
value to HyprlandMonitor[] before returning.

---

Duplicate comments:
In `@src/renderer/subtitle-render.test.ts`:
- Around line 1105-1110: The test loop that iterates annotationClass in
subtitle-render.test.ts omits the 'word-name-match' case, so add
'word-name-match' to the array of classes used in that for loop (the array
initialized where annotationClass is iterated) so the JLPT underline lock path
for name-match tokens is exercised; update any related assertions in the same
loop to expect the same JLPT-lock behavior for 'word-name-match' as for the
other word-* classes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: df48e7a1-57bb-4358-be0c-1e7f9a118691

📥 Commits

Reviewing files that changed from the base of the PR and between 4cb7b53 and 6e666d7.

⛔ Files ignored due to path filters (75)
  • backlog/completed/task-336 - Fix-Hyprland-fullscreen-overlay-downward-offset.md is excluded by !backlog/**
  • backlog/completed/task-339 - Stop-pinning-Hyprland-overlay-windows.md is excluded by !backlog/**
  • backlog/tasks/task-304 - Fix-N1-sentence-boundary-counting-across-Yomitan-punctuation-gaps.md is excluded by !backlog/**
  • backlog/tasks/task-305 - Use-Yomitan-word-classes-for-subtitle-token-POS-filtering.md is excluded by !backlog/**
  • backlog/tasks/task-306 - Fix-Hyprland-fullscreen-overlay-geometry-and-hover-pause.md is excluded by !backlog/**
  • backlog/tasks/task-307 - Exclude-kana-only-words-from-N1-subtitle-targets.md is excluded by !backlog/**
  • backlog/tasks/task-308 - Restore-persistent-JLPT-subtitle-underlines.md is excluded by !backlog/**
  • backlog/tasks/task-309 - Accept-modified-follow-up-digits-for-multi-line-sentence-mining.md is excluded by !backlog/**
  • backlog/tasks/task-310 - Suppress-N1-highlight-for-kana-only-candidate-sentences.md is excluded by !backlog/**
  • backlog/tasks/task-311 - Suppress-auxiliary-inflection-fragments-from-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-312 - Suppress-ja-nai-explanatory-ending-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-315 - Suppress-annotations-for-standalone-じゃない-and-です-ending-tokens.md is excluded by !backlog/**
  • backlog/tasks/task-316 - Fix-macOS-launcher-playback-exit-with-background-stats-daemon.md is excluded by !backlog/**
  • backlog/tasks/task-318 - Keep-JLPT-underline-color-fixed-after-lookup-selection.md is excluded by !backlog/**
  • backlog/tasks/task-319 - Suppress-annotations-for-expressive-interjection-subtitles.md is excluded by !backlog/**
  • backlog/tasks/task-320 - Refresh-current-subtitle-known-word-highlight-after-successful-mining.md is excluded by !backlog/**
  • backlog/tasks/task-321 - Preserve-word-audio-during-manual-clipboard-card-updates.md is excluded by !backlog/**
  • backlog/tasks/task-322 - Fix-failing-CI-checks-on-PR-57.md is excluded by !backlog/**
  • backlog/tasks/task-323 - Fix-macOS-overlay-hiding-while-mpv-remains-active.md is excluded by !backlog/**
  • backlog/tasks/task-324 - Fix-mpv-playlist-changes-re-running-app-warmups.md is excluded by !backlog/**
  • backlog/tasks/task-325 - Keep-JLPT-underline-color-fixed-with-combined-lookup-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-326 - Fix-AniList-post-watch-update-after-skipped-completion-threshold.md is excluded by !backlog/**
  • backlog/tasks/task-327 - Restore-stats-daemon-deferral-when-launching-playback.md is excluded by !backlog/**
  • backlog/tasks/task-328 - Keep-subtitle-prefetch-running-after-immediate-cached-annotation-render.md is excluded by !backlog/**
  • backlog/tasks/task-331 - Address-unresolved-CodeRabbit-comments-on-PR-57.md is excluded by !backlog/**
  • backlog/tasks/task-332 - Fix-subtitle-frequency-annotation-missing-ranks-shown-in-Yomitan-popup.md is excluded by !backlog/**
  • backlog/tasks/task-333 - Suppress-aru-subtitle-annotations.md is excluded by !backlog/**
  • backlog/tasks/task-334 - Assess-and-address-PR-57-latest-CodeRabbit-comments.md is excluded by !backlog/**
  • backlog/tasks/task-335 - Fix-Linux-AniList-setup-gate-using-stored-keyring-token.md is excluded by !backlog/**
  • backlog/tasks/task-337 - Fix-transient-Linux-safeStorage-failure-poisoning-AniList-token-store.md is excluded by !backlog/**
  • backlog/tasks/task-338 - Fix-known-word-highlight-on-standalone-subtitle-particles.md is excluded by !backlog/**
  • backlog/tasks/task-340 - Restore-default-replay-and-next-subtitle-overlay-keybindings.md is excluded by !backlog/**
  • backlog/tasks/task-341 - Fix-frequency-highlight-for-honorific-prefix-noun-tokens.md is excluded by !backlog/**
  • backlog/tasks/task-343 - Fix-macOS-character-dictionary-selector-session-shortcut.md is excluded by !backlog/**
  • backlog/tasks/task-344 - Fix-macOS-overlay-tracker-hiding-while-mpv-remains-active.md is excluded by !backlog/**
  • backlog/tasks/task-345 - Address-PR-57-latest-CodeRabbit-review-comments.md is excluded by !backlog/**
  • backlog/tasks/task-346 - Fix-stats-session-detail-when-recent-media-is-missing.md is excluded by !backlog/**
  • changes/305-tokenizer-word-class-pos-filtering.md is excluded by !changes/**
  • changes/306-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/307-kana-nplusone-targets.md is excluded by !changes/**
  • changes/308-jlpt-underlines.md is excluded by !changes/**
  • changes/309-multi-mine-modified-digits.md is excluded by !changes/**
  • changes/311-auxiliary-inflection-annotation-filter.md is excluded by !changes/**
  • changes/312-grammar-ending-annotation-filter.md is excluded by !changes/**
  • changes/316-macos-playback-stats-daemon.md is excluded by !changes/**
  • changes/318-jlpt-underline-selection.md is excluded by !changes/**
  • changes/319-interjection-annotation-filter.md is excluded by !changes/**
  • changes/320-current-subtitle-known-highlight.md is excluded by !changes/**
  • changes/321-grammar-ending-pattern-filter.md is excluded by !changes/**
  • changes/322-preserve-word-audio-manual-update.md is excluded by !changes/**
  • changes/323-macos-overlay-tracker-flaps.md is excluded by !changes/**
  • changes/324-mpv-playlist-overlay-reuse.md is excluded by !changes/**
  • changes/325-jlpt-combined-annotation-underlines.md is excluded by !changes/**
  • changes/326-anilist-time-position-post-watch.md is excluded by !changes/**
  • changes/327-stats-daemon-deferral.md is excluded by !changes/**
  • changes/328-subtitle-prefetch-cache-hit-resume.md is excluded by !changes/**
  • changes/332-subtitle-frequency-ordinal-prefix.md is excluded by !changes/**
  • changes/333-aru-annotation-filter.md is excluded by !changes/**
  • changes/334-coderabbit-followups.md is excluded by !changes/**
  • changes/335-anilist-linux-token-setup.md is excluded by !changes/**
  • changes/336-hyprland-fullscreen-overlay.md is excluded by !changes/**
  • changes/336-overlay-focus-ring.md is excluded by !changes/**
  • changes/337-anilist-safe-storage-retry.md is excluded by !changes/**
  • changes/338-known-word-particle-highlights.md is excluded by !changes/**
  • changes/339-hyprland-overlay-pin.md is excluded by !changes/**
  • changes/340-default-subtitle-keybindings.md is excluded by !changes/**
  • changes/344-macos-overlay-active-mpv.md is excluded by !changes/**
  • changes/346-stats-session-detail.md is excluded by !changes/**
  • docs-site/anki-integration.md is excluded by !docs-site/**
  • docs-site/configuration.md is excluded by !docs-site/**
  • docs-site/mining-workflow.md is excluded by !docs-site/**
  • docs-site/public/config.example.jsonc is excluded by !docs-site/**
  • docs-site/shortcuts.md is excluded by !docs-site/**
  • docs-site/troubleshooting.md is excluded by !docs-site/**
  • docs-site/usage.md is excluded by !docs-site/**
📒 Files selected for processing (93)
  • config.example.jsonc
  • package.json
  • plugin/subminer/lifecycle.lua
  • plugin/subminer/process.lua
  • plugin/subminer/session_bindings.lua
  • scripts/test-plugin-session-bindings.lua
  • scripts/test-plugin-start-gate.lua
  • src/anki-integration.test.ts
  • src/anki-integration.ts
  • src/anki-integration/card-creation-manual-update.test.ts
  • src/anki-integration/card-creation.ts
  • src/anki-integration/known-word-cache.test.ts
  • src/anki-integration/known-word-cache.ts
  • src/config/definitions/defaults-core.ts
  • src/config/definitions/domain-registry.test.ts
  • src/core/services/anilist/anilist-token-store.test.ts
  • src/core/services/anilist/anilist-token-store.ts
  • src/core/services/anilist/anilist-updater.test.ts
  • src/core/services/anilist/anilist-updater.ts
  • src/core/services/hyprland-window-placement.test.ts
  • src/core/services/hyprland-window-placement.ts
  • src/core/services/immersion-tracker/__tests__/query.test.ts
  • src/core/services/immersion-tracker/query-library.ts
  • src/core/services/ipc.test.ts
  • src/core/services/ipc.ts
  • src/core/services/mpv-properties.ts
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/mpv-protocol.ts
  • src/core/services/mpv.test.ts
  • src/core/services/mpv.ts
  • src/core/services/overlay-manager.test.ts
  • src/core/services/overlay-visibility.test.ts
  • src/core/services/overlay-visibility.ts
  • src/core/services/overlay-window-config.test.ts
  • src/core/services/overlay-window-options.ts
  • src/core/services/overlay-window.ts
  • src/core/services/session-bindings.test.ts
  • src/core/services/stats-window-runtime.ts
  • src/core/services/stats-window.test.ts
  • src/core/services/stats-window.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • src/core/services/tokenizer/grammar-ending.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/tokenizer/parser-selection-stage.test.ts
  • src/core/services/tokenizer/parser-selection-stage.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/main.ts
  • src/main/dependencies.ts
  • src/main/runtime/anilist-post-watch.test.ts
  • src/main/runtime/anilist-post-watch.ts
  • src/main/runtime/anilist-setup-protocol-main-deps.test.ts
  • src/main/runtime/anilist-setup-protocol.test.ts
  • src/main/runtime/anilist-setup-protocol.ts
  • src/main/runtime/anilist-setup.test.ts
  • src/main/runtime/anilist-setup.ts
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/main/runtime/app-lifecycle-actions.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/main/runtime/composers/anilist-setup-composer.test.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/runtime/mpv-client-event-bindings.ts
  • src/main/runtime/mpv-main-event-actions.test.ts
  • src/main/runtime/mpv-main-event-actions.ts
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/main/runtime/startup-config.test.ts
  • src/main/runtime/startup-config.ts
  • src/main/runtime/stats-server-routing.test.ts
  • src/main/runtime/stats-server-routing.ts
  • src/renderer/handlers/keyboard.test.ts
  • src/renderer/handlers/keyboard.ts
  • src/renderer/handlers/mouse.test.ts
  • src/renderer/handlers/mouse.ts
  • src/renderer/overlay-legacy-cleanup.test.ts
  • src/renderer/style.css
  • src/renderer/subtitle-render.test.ts
  • src/token-merger.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/window-trackers/hyprland-tracker.ts
  • stats/src/App.tsx
  • stats/src/styles/globals.css
  • stats/src/styles/globals.test.ts
💤 Files with no reviewable changes (1)
  • src/core/services/anilist/anilist-token-store.ts
✅ Files skipped from review due to trivial changes (19)
  • src/main/runtime/anilist-setup-protocol.test.ts
  • stats/src/styles/globals.css
  • src/core/services/overlay-window-options.ts
  • src/core/services/mpv-properties.ts
  • src/config/definitions/domain-registry.test.ts
  • src/main/runtime/startup-config.ts
  • src/core/services/hyprland-window-placement.test.ts
  • src/main/runtime/mpv-client-event-bindings.test.ts
  • src/main/runtime/anilist-setup-protocol-main-deps.test.ts
  • stats/src/App.tsx
  • src/main/runtime/app-lifecycle-actions.test.ts
  • src/main/runtime/mpv-main-event-main-deps.test.ts
  • src/main/runtime/composers/startup-lifecycle-composer.test.ts
  • src/main/runtime/stats-server-routing.test.ts
  • src/core/services/overlay-window-config.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.test.ts
  • src/core/services/mpv-protocol.test.ts
  • src/core/services/stats-window.test.ts
  • src/core/services/tokenizer.ts
🚧 Files skipped from review as they are similar to previous changes (59)
  • src/main/runtime/composers/anilist-setup-composer.test.ts
  • src/main/runtime/app-lifecycle-actions.ts
  • src/renderer/overlay-legacy-cleanup.test.ts
  • config.example.jsonc
  • src/main/runtime/mpv-main-event-bindings.ts
  • src/anki-integration/known-word-cache.test.ts
  • src/main/runtime/stats-server-routing.ts
  • src/config/definitions/defaults-core.ts
  • src/main/dependencies.ts
  • src/core/services/mpv-protocol.ts
  • src/core/services/anilist/anilist-token-store.test.ts
  • src/anki-integration.test.ts
  • src/core/services/overlay-manager.test.ts
  • src/renderer/handlers/keyboard.ts
  • src/core/services/tokenizer/parser-selection-stage.ts
  • src/core/services/session-bindings.test.ts
  • src/main/runtime/startup-config.test.ts
  • src/core/services/mpv.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/mpv-main-event-actions.ts
  • src/renderer/handlers/mouse.ts
  • src/core/services/anilist/anilist-updater.test.ts
  • src/main/runtime/anilist-setup-protocol.ts
  • stats/src/styles/globals.test.ts
  • src/main/runtime/app-lifecycle-main-cleanup.ts
  • src/main/runtime/app-lifecycle-main-cleanup.test.ts
  • src/anki-integration/card-creation-manual-update.test.ts
  • src/renderer/handlers/keyboard.test.ts
  • plugin/subminer/lifecycle.lua
  • src/core/services/ipc.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/core/services/tokenizer/parser-enrichment-stage.ts
  • src/core/services/mpv.test.ts
  • src/anki-integration.ts
  • src/core/services/ipc.test.ts
  • src/token-merger.ts
  • src/core/services/tokenizer/grammar-ending.ts
  • src/main/runtime/mpv-main-event-main-deps.ts
  • src/renderer/style.css
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/core/services/stats-window-runtime.ts
  • src/anki-integration/card-creation.ts
  • src/main/runtime/anilist-post-watch.test.ts
  • src/anki-integration/known-word-cache.ts
  • plugin/subminer/session_bindings.lua
  • src/main/runtime/anilist-post-watch.ts
  • src/core/services/hyprland-window-placement.ts
  • src/renderer/handlers/mouse.test.ts
  • src/main/runtime/anilist-setup.ts
  • package.json
  • scripts/test-plugin-start-gate.lua
  • src/core/services/overlay-window.ts
  • src/core/services/tokenizer/annotation-stage.ts
  • plugin/subminer/process.lua
  • src/core/services/stats-window.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
  • src/core/services/tokenizer/annotation-stage.test.ts
  • src/core/services/tokenizer/subtitle-annotation-filter.ts
  • src/core/services/tokenizer.test.ts

Comment thread src/core/services/immersion-tracker/query-library.ts Outdated
Comment thread src/main.ts Outdated
Comment thread src/window-trackers/hyprland-tracker.ts
Comment thread src/window-trackers/hyprland-tracker.ts
- use filtered word counts in media detail session token aggregation
- cancel fullscreen refresh burst on exit via updateLinuxMpvFullscreenOverlayRefreshBurst
- guard Hyprland JSON.parse in try/catch; exclude windowtitle from geometry events
- narrow focus suppression from :focus to :focus-visible
- apply JLPT lock selectors to word-name-match tokens (N1–N5)

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts (1)

83-86: ⚡ Quick win

Cancellation assertion window is too short to cover the full burst horizon.

This waits only 80ms, but the burst includes delayed work up to 600ms, so late leaked timers can slip through.

Suggested adjustment
-    await new Promise((resolve) => setTimeout(resolve, 80));
+    await new Promise((resolve) => setTimeout(resolve, 650));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts` around lines
83 - 86, The test currently waits only 80ms before asserting that nextCancel is
null and calls is empty, which is too short because the burst includes delayed
work up to ~600ms; update the wait in linux-mpv-fullscreen-overlay-refresh.test
(the promise before asserting nextCancel and calls) to exceed the burst horizon
(e.g., >=600ms, use ~700ms or a helper to flush timers) so late timers cannot
leak into the assertions for nextCancel and calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts`:
- Around line 83-86: The test currently waits only 80ms before asserting that
nextCancel is null and calls is empty, which is too short because the burst
includes delayed work up to ~600ms; update the wait in
linux-mpv-fullscreen-overlay-refresh.test (the promise before asserting
nextCancel and calls) to exceed the burst horizon (e.g., >=600ms, use ~700ms or
a helper to flush timers) so late timers cannot leak into the assertions for
nextCancel and calls.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3f4db0bb-6ac2-49f7-83d1-aac8a9e838d8

📥 Commits

Reviewing files that changed from the base of the PR and between 6e666d7 and 6bf9051.

⛔ Files ignored due to path filters (2)
  • backlog/tasks/task-347 - Address-PR-57-CodeRabbit-review-round-after-stats-session-fix.md is excluded by !backlog/**
  • backlog/tasks/task-348 - Fix-PR-57-coverage-CI-focus-chrome-failure.md is excluded by !backlog/**
📒 Files selected for processing (10)
  • src/core/services/immersion-tracker/__tests__/query.test.ts
  • src/core/services/immersion-tracker/query-library.ts
  • src/main.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.test.ts
  • src/main/runtime/linux-mpv-fullscreen-overlay-refresh.ts
  • src/renderer/overlay-legacy-cleanup.test.ts
  • src/renderer/style.css
  • src/renderer/subtitle-render.test.ts
  • src/window-trackers/hyprland-tracker.test.ts
  • src/window-trackers/hyprland-tracker.ts
✅ Files skipped from review due to trivial changes (1)
  • src/core/services/immersion-tracker/tests/query.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/window-trackers/hyprland-tracker.test.ts
  • src/renderer/style.css
  • src/window-trackers/hyprland-tracker.ts

- Release always-on-top when tracked mpv loses foreground on macOS
- Skip visible overlay blur restacking on macOS to avoid covering unrelated windows
- Prefer Yomitan internal parse tokens over fragmented scanner output for known-word decisions
- Add regression tests for both behaviors

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/core/services/overlay-window-input.ts`:
- Around line 73-75: The early-return is firing a Windows-specific callback on
macOS because the condition combines platform checks; change the logic so macOS
still early-returns for visible overlays but does NOT call
onWindowsVisibleOverlayBlur, and only invoke options.onWindowsVisibleOverlayBlur
when platform === 'win32' and options.kind === 'visible'. Update the
conditional(s) around the visible-overlay handling in overlay-window-input.ts to
separate the darwin early-return from the win32 callback invocation (referencing
the symbols platform, options.kind, and options.onWindowsVisibleOverlayBlur).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: a01be843-c892-4ba2-a15c-2a3db5470c69

📥 Commits

Reviewing files that changed from the base of the PR and between 6bf9051 and ca796bf.

⛔ Files ignored due to path filters (4)
  • backlog/tasks/task-349 - Fix-macOS-overlay-window-ordering-behind-foreground-apps.md is excluded by !backlog/**
  • backlog/tasks/task-350 - Fix-known-highlighting-for-Yomitan-compound-tokens.md is excluded by !backlog/**
  • changes/349-macos-overlay-z-order.md is excluded by !changes/**
  • changes/350-known-yomitan-token-highlighting.md is excluded by !changes/**
📒 Files selected for processing (7)
  • src/core/services/overlay-visibility.test.ts
  • src/core/services/overlay-visibility.ts
  • src/core/services/overlay-window-input.ts
  • src/core/services/overlay-window.test.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.test.ts
  • src/core/services/tokenizer/yomitan-parser-runtime.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/core/services/overlay-visibility.test.ts
  • src/core/services/tokenizer.test.ts
  • src/core/services/overlay-visibility.ts

Comment thread src/core/services/overlay-window-input.ts Outdated
- Split win32/darwin branches in handleOverlayWindowBlurred so darwin visible blur returns early without calling onWindowsVisibleOverlayBlur
- Add regression test asserting Windows callback stays inactive on macOS visible overlay blur
- Close TASK-347
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant