update docs/backlog

This commit is contained in:
2026-02-28 21:15:26 -08:00
parent cbff3f9ad9
commit a80d6dbea9
19 changed files with 255 additions and 211 deletions

View File

@@ -1,11 +1,11 @@
project_name: "SubMiner" project_name: 'SubMiner'
default_status: "To Do" default_status: 'To Do'
statuses: ["To Do", "In Progress", "Done"] statuses: ['To Do', 'In Progress', 'Done']
labels: [] labels: []
definition_of_done: [] definition_of_done: []
date_format: yyyy-mm-dd date_format: yyyy-mm-dd
max_column_width: 20 max_column_width: 20
default_editor: "nvim" default_editor: 'nvim'
auto_open_browser: false auto_open_browser: false
default_port: 6420 default_port: 6420
remote_operations: true remote_operations: true
@@ -13,4 +13,4 @@ auto_commit: false
bypass_git_hooks: false bypass_git_hooks: false
check_active_branches: true check_active_branches: true
active_branch_days: 30 active_branch_days: 30
task_prefix: "task" task_prefix: 'task'

View File

@@ -26,15 +26,18 @@ ordinal: 1000
## Description ## Description
<!-- SECTION:DESCRIPTION:BEGIN --> <!-- SECTION:DESCRIPTION:BEGIN -->
Scope: Branch-only commits main..HEAD on refactor-overlay (a14c9da through 9e4e588) rebuilt overlay behavior around visible overlay mode and removed legacy invisible overlay paths. Scope: Branch-only commits main..HEAD on refactor-overlay (a14c9da through 9e4e588) rebuilt overlay behavior around visible overlay mode and removed legacy invisible overlay paths.
Delivered behavior: Delivered behavior:
- Removed renderer invisible overlay layout/offset helpers and main hover-highlight runtime code paths. - Removed renderer invisible overlay layout/offset helpers and main hover-highlight runtime code paths.
- Added explicit overlay-to-mpv subtitle visibility synchronization so visible overlay state controls primary subtitle visibility consistently. - Added explicit overlay-to-mpv subtitle visibility synchronization so visible overlay state controls primary subtitle visibility consistently.
- Hardened overlay runtime/bootstrap lifecycle around modal fallback open state and bridge send path edge cases. - Hardened overlay runtime/bootstrap lifecycle around modal fallback open state and bridge send path edge cases.
- Updated plugin/config/docs defaults to reflect visible-overlay-first behavior and subtitle binding controls. - Updated plugin/config/docs defaults to reflect visible-overlay-first behavior and subtitle binding controls.
Risk/impact context: Risk/impact context:
- Large cross-layer refactor touching runtime wiring, renderer event handling, and plugin behavior. - Large cross-layer refactor touching runtime wiring, renderer event handling, and plugin behavior.
- Regression coverage added/updated for overlay runtime, mpv protocol handling, renderer cleanup, and subtitle rendering paths. - Regression coverage added/updated for overlay runtime, mpv protocol handling, renderer cleanup, and subtitle rendering paths.
<!-- SECTION:DESCRIPTION:END --> <!-- SECTION:DESCRIPTION:END -->
@@ -42,5 +45,7 @@ Risk/impact context:
## Final Summary ## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN --> <!-- SECTION:FINAL_SUMMARY:BEGIN -->
Completed and validated in branch commit set before merge. Refactor reduces dead overlay modes, centralizes subtitle visibility behavior, and documents new defaults/constraints. Completed and validated in branch commit set before merge. Refactor reduces dead overlay modes, centralizes subtitle visibility behavior, and documents new defaults/constraints.
<!-- SECTION:FINAL_SUMMARY:END --> <!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -25,9 +25,11 @@ ordinal: 2000
## Description ## Description
<!-- SECTION:DESCRIPTION:BEGIN --> <!-- SECTION:DESCRIPTION:BEGIN -->
Scope: Current unmerged working-tree changes implement an optional local AnkiConnect-compatible proxy and transport switching for card enrichment. Scope: Current unmerged working-tree changes implement an optional local AnkiConnect-compatible proxy and transport switching for card enrichment.
Delivered behavior: Delivered behavior:
- Added proxy server that forwards AnkiConnect requests and enqueues addNote/addNotes note IDs for post-create enrichment, with de-duplication and loop-configuration protection. - Added proxy server that forwards AnkiConnect requests and enqueues addNote/addNotes note IDs for post-create enrichment, with de-duplication and loop-configuration protection.
- Added follow-up response-shape compatibility handling so proxy enqueue works for both envelope (`{result,error}`) and bare JSON payloads, including `multi` variants. - Added follow-up response-shape compatibility handling so proxy enqueue works for both envelope (`{result,error}`) and bare JSON payloads, including `multi` variants.
- Added config schema/defaults/resolution for ankiConnect.proxy (enabled, host, port, upstreamUrl) with validation warnings and fallback behavior. - Added config schema/defaults/resolution for ankiConnect.proxy (enabled, host, port, upstreamUrl) with validation warnings and fallback behavior.
@@ -36,6 +38,7 @@ Delivered behavior:
- Updated user docs/config examples for proxy mode setup, troubleshooting, and mining workflow behavior. - Updated user docs/config examples for proxy mode setup, troubleshooting, and mining workflow behavior.
Risk/impact context: Risk/impact context:
- New network surface on local host/port; correctness depends on safe proxy upstream configuration and robust response handling. - New network surface on local host/port; correctness depends on safe proxy upstream configuration and robust response handling.
- Tests added for proxy queue behavior, config resolution, and parser sync routines. - Tests added for proxy queue behavior, config resolution, and parser sync routines.
<!-- SECTION:DESCRIPTION:END --> <!-- SECTION:DESCRIPTION:END -->
@@ -43,5 +46,7 @@ Risk/impact context:
## Final Summary ## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN --> <!-- SECTION:FINAL_SUMMARY:BEGIN -->
Completed implementation in branch working tree; ready to merge once local changes are committed and test gate passes. Completed implementation in branch working tree; ready to merge once local changes are committed and test gate passes.
<!-- SECTION:FINAL_SUMMARY:END --> <!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -19,19 +19,24 @@ ordinal: 3000
## Description ## Description
<!-- SECTION:DESCRIPTION:BEGIN --> <!-- SECTION:DESCRIPTION:BEGIN -->
Scope: Commit cc2f9ef improves startup config-warning visibility on macOS by ensuring full details are surfaced in the native UI path and reflected in docs. Scope: Commit cc2f9ef improves startup config-warning visibility on macOS by ensuring full details are surfaced in the native UI path and reflected in docs.
Delivered behavior: Delivered behavior:
- Config validation/runtime wiring updated so macOS users can access complete warning details instead of truncated notification-only text. - Config validation/runtime wiring updated so macOS users can access complete warning details instead of truncated notification-only text.
- Added/updated tests around config validation and startup config warning flows. - Added/updated tests around config validation and startup config warning flows.
- Updated configuration docs to clarify platform-specific warning presentation behavior. - Updated configuration docs to clarify platform-specific warning presentation behavior.
Risk/impact context: Risk/impact context:
- Low runtime risk; primarily user-facing diagnostics clarity improvement. - Low runtime risk; primarily user-facing diagnostics clarity improvement.
<!-- SECTION:DESCRIPTION:END --> <!-- SECTION:DESCRIPTION:END -->
## Final Summary ## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN --> <!-- SECTION:FINAL_SUMMARY:BEGIN -->
Completed small follow-up fix to reduce config-debug friction on macOS. Completed small follow-up fix to reduce config-debug friction on macOS.
<!-- SECTION:FINAL_SUMMARY:END --> <!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -39,9 +39,11 @@ ordinal: 4000
## Description ## Description
<!-- SECTION:DESCRIPTION:BEGIN --> <!-- SECTION:DESCRIPTION:BEGIN -->
Scope: Replace monolithic `plugin/subminer.lua` with modular plugin runtime; optimize command execution paths; align install/docs/tests; fix launcher smoke instability. Scope: Replace monolithic `plugin/subminer.lua` with modular plugin runtime; optimize command execution paths; align install/docs/tests; fix launcher smoke instability.
Delivered behavior: Delivered behavior:
- Full plugin cutover to `plugin/subminer/main.lua` + module directory (no runtime compatibility shim with old monolith file). - Full plugin cutover to `plugin/subminer/main.lua` + module directory (no runtime compatibility shim with old monolith file).
- Process/control command path moved toward async subprocess usage for non-start actions (`stop`, `toggle`, `settings`, restart stop leg), reducing synchronous blocking in mpv script runtime. - Process/control command path moved toward async subprocess usage for non-start actions (`stop`, `toggle`, `settings`, restart stop leg), reducing synchronous blocking in mpv script runtime.
- AniSkip path guarded: lookup runs only in SubMiner context (launcher metadata, explicit script-message refresh, or detected running app), instead of every opened file. - AniSkip path guarded: lookup runs only in SubMiner context (launcher metadata, explicit script-message refresh, or detected running app), instead of every opened file.
@@ -53,6 +55,7 @@ Delivered behavior:
- Playback command cleanup race fixed when mpv exits before exit-listener registration. - Playback command cleanup race fixed when mpv exits before exit-listener registration.
Risk/impact context: Risk/impact context:
- mpv plugin loading path changed from single-file to module directory; packaging/install paths must stay consistent with release assets. - mpv plugin loading path changed from single-file to module directory; packaging/install paths must stay consistent with release assets.
- Async control/AniSkip path changes reduce blocking but can surface timing differences; regression checks added for cold start, file-load gating, and explicit refresh behavior. - Async control/AniSkip path changes reduce blocking but can surface timing differences; regression checks added for cold start, file-load gating, and explicit refresh behavior.
<!-- SECTION:DESCRIPTION:END --> <!-- SECTION:DESCRIPTION:END -->
@@ -60,18 +63,22 @@ Risk/impact context:
## Final Summary ## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN --> <!-- SECTION:FINAL_SUMMARY:BEGIN -->
AniSkip gate/async update delivered in plugin runtime: AniSkip gate/async update delivered in plugin runtime:
- `plugin/subminer/lifecycle.lua`: deferred AniSkip fetch and overlay-start trigger. - `plugin/subminer/lifecycle.lua`: deferred AniSkip fetch and overlay-start trigger.
- `plugin/subminer/aniskip.lua`: async lookup pipeline + context guard + session caches. - `plugin/subminer/aniskip.lua`: async lookup pipeline + context guard + session caches.
- `plugin/subminer/environment.lua`: async app-running detection with short cache. - `plugin/subminer/environment.lua`: async app-running detection with short cache.
- `plugin/subminer/messages.lua`: explicit script-message trigger wiring. - `plugin/subminer/messages.lua`: explicit script-message trigger wiring.
Regression coverage updated: Regression coverage updated:
- `scripts/test-plugin-start-gate.lua` now verifies: - `scripts/test-plugin-start-gate.lua` now verifies:
- no sync `ps`/`curl` on non-context file load - no sync `ps`/`curl` on non-context file load
- no AniSkip network lookup on non-context file load - no AniSkip network lookup on non-context file load
- script-message refresh forces async AniSkip lookup - script-message refresh forces async AniSkip lookup
Validation run: Validation run:
- `bun run test:plugin:src` pass. - `bun run test:plugin:src` pass.
<!-- SECTION:FINAL_SUMMARY:END --> <!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,9 +1,10 @@
--- ---
id: TASK-74 id: TASK-74
title: 'Startup warmups: configurable warmup vs defer with low-power mode' title: 'Startup warmups: configurable warmup vs defer with low-power mode'
status: In Progress status: Done
assignee: [] assignee: []
created_date: '2026-02-27 21:05' created_date: '2026-02-27 21:05'
updated_date: '2026-03-01 04:14'
labels: [] labels: []
dependencies: [] dependencies: []
references: references:
@@ -22,14 +23,17 @@ references:
- src/main/runtime/startup-warmups-main-deps.test.ts - src/main/runtime/startup-warmups-main-deps.test.ts
- src/core/services/app-ready.test.ts - src/core/services/app-ready.test.ts
priority: medium priority: medium
ordinal: 7000
--- ---
## Description ## Description
<!-- SECTION:DESCRIPTION:BEGIN --> <!-- SECTION:DESCRIPTION:BEGIN -->
Add startup warmup controls to allow per-integration warmup or deferred first-use loading. Add startup warmup controls to allow per-integration warmup or deferred first-use loading.
Scope: Scope:
- New config section `startupWarmups` with toggles for `mecab`, `yomitanExtension`, `subtitleDictionaries`, and `jellyfinRemoteSession`. - New config section `startupWarmups` with toggles for `mecab`, `yomitanExtension`, `subtitleDictionaries`, and `jellyfinRemoteSession`.
- New `startupWarmups.lowPowerMode` policy: defer everything except Yomitan extension. - New `startupWarmups.lowPowerMode` policy: defer everything except Yomitan extension.
- Keep default behavior as full warmup. - Keep default behavior as full warmup.
@@ -40,7 +44,9 @@ Scope:
## Final Summary ## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN --> <!-- SECTION:FINAL_SUMMARY:BEGIN -->
Implemented: Implemented:
- Added `startupWarmups` to config types/defaults/options/template/resolve. - Added `startupWarmups` to config types/defaults/options/template/resolve.
- Warmup scheduler now uses per-integration gating functions. - Warmup scheduler now uses per-integration gating functions.
- Low-power mode now defers MeCab, subtitle dictionaries, and Jellyfin remote session warmups while still warming Yomitan extension. - Low-power mode now defers MeCab, subtitle dictionaries, and Jellyfin remote session warmups while still warming Yomitan extension.
@@ -48,11 +54,13 @@ Implemented:
- Added/updated tests across config and runtime warmup modules. - Added/updated tests across config and runtime warmup modules.
Validation: Validation:
- `bun run test:config:src` - `bun run test:config:src`
- `bun run test:core:src` - `bun run test:core:src`
- `tsc --noEmit` - `tsc --noEmit`
Follow-up updates: Follow-up updates:
- Startup now triggers warmups earlier in app-ready flow (right after config validation/log-level setup) instead of waiting for initial args/overlay actions. Goal: tokenization warmup is already done or mostly done by first visible-subs toggle. - Startup now triggers warmups earlier in app-ready flow (right after config validation/log-level setup) instead of waiting for initial args/overlay actions. Goal: tokenization warmup is already done or mostly done by first visible-subs toggle.
- Tokenization warmup scheduling consolidated as `subtitle-tokenization` stage; when enabled by toggles, it runs Yomitan extension first, then MeCab/dictionary warmups. - Tokenization warmup scheduling consolidated as `subtitle-tokenization` stage; when enabled by toggles, it runs Yomitan extension first, then MeCab/dictionary warmups.
- Added per-stage debug logs for warmup progress and skip reasons: - Added per-stage debug logs for warmup progress and skip reasons:
@@ -65,4 +73,4 @@ Follow-up updates:
- `src/main/runtime/startup-warmups.test.ts` - `src/main/runtime/startup-warmups.test.ts`
- `src/main/runtime/startup-warmups-main-deps.test.ts` - `src/main/runtime/startup-warmups-main-deps.test.ts`
- `src/core/services/app-ready.test.ts` - `src/core/services/app-ready.test.ts`
<!-- SECTION:FINAL_SUMMARY:END --> <!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -4,24 +4,29 @@ title: 'Tokenizer: configurable POS exclusions for N+1 and frequency annotations
status: Done status: Done
assignee: [] assignee: []
created_date: '2026-03-01 01:23' created_date: '2026-03-01 01:23'
updated_date: '2026-03-01 01:32' updated_date: '2026-03-01 04:14'
labels: [] labels: []
dependencies: [] dependencies: []
priority: medium priority: medium
ordinal: 6000
--- ---
## Description ## Description
<!-- SECTION:DESCRIPTION:BEGIN --> <!-- SECTION:DESCRIPTION:BEGIN -->
N+1 and frequency highlighting should ignore non-learning tokens (e.g., particles/auxiliary forms) based on MeCab POS1 tags, while remaining user-configurable. N+1 and frequency highlighting should ignore non-learning tokens (e.g., particles/auxiliary forms) based on MeCab POS1 tags, while remaining user-configurable.
Problem example: for subtitle phrase containing になれば, the highlighted N+1 target should not be the non-useful inflection/token piece when POS indicates an excluded class. Problem example: for subtitle phrase containing になれば, the highlighted N+1 target should not be the non-useful inflection/token piece when POS indicates an excluded class.
Implement configurable exclusion defaults with add/remove overrides so users can tune behavior without code changes. Implement configurable exclusion defaults with add/remove overrides so users can tune behavior without code changes.
<!-- SECTION:DESCRIPTION:END --> <!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria ## Acceptance Criteria
<!-- AC:BEGIN --> <!-- AC:BEGIN -->
- [x] #1 Default exclusion set omits non-useful POS1 classes from both N+1 candidate selection and frequency highlighting. - [x] #1 Default exclusion set omits non-useful POS1 classes from both N+1 candidate selection and frequency highlighting.
- [x] #2 Users can add extra POS1 exclusions and remove defaults via config. - [x] #2 Users can add extra POS1 exclusions and remove defaults via config.
- [x] #3 Tokenizer/annotation tests cover default behavior and config add/remove overrides. - [x] #3 Tokenizer/annotation tests cover default behavior and config add/remove overrides.
@@ -30,5 +35,7 @@ Implement configurable exclusion defaults with add/remove overrides so users can
## Final Summary ## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN --> <!-- SECTION:FINAL_SUMMARY:BEGIN -->
Implemented configurable annotation POS exclusions with defaults+add/remove for both MeCab POS1 and POS2, wired to N+1 candidate selection and frequency highlighting. Added POS2 default exclusion (非自立), expanded POS1 defaults for function words, added Yomitan->MeCab enrichment to carry pos2/pos3 metadata, updated config docs/examples, and added regression tests including になれば case. Implemented configurable annotation POS exclusions with defaults+add/remove for both MeCab POS1 and POS2, wired to N+1 candidate selection and frequency highlighting. Added POS2 default exclusion (非自立), expanded POS1 defaults for function words, added Yomitan->MeCab enrichment to carry pos2/pos3 metadata, updated config docs/examples, and added regression tests including になれば case.
<!-- SECTION:FINAL_SUMMARY:END --> <!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -4,22 +4,27 @@ title: 'Tokenizer: remove POS exclusion config surface and keep hardcoded defaul
status: Done status: Done
assignee: [] assignee: []
created_date: '2026-03-01 02:45' created_date: '2026-03-01 02:45'
updated_date: '2026-03-01 02:51' updated_date: '2026-03-01 04:14'
labels: [] labels: []
dependencies: [] dependencies: []
priority: medium priority: medium
ordinal: 5000
--- ---
## Description ## Description
<!-- SECTION:DESCRIPTION:BEGIN --> <!-- SECTION:DESCRIPTION:BEGIN -->
Remove user-facing config keys for annotation POS exclusions. Keep N+1/frequency POS exclusion behavior as built-in defaults with no config required. Remove user-facing config keys for annotation POS exclusions. Keep N+1/frequency POS exclusion behavior as built-in defaults with no config required.
Scope: remove config parsing/registry/docs/example for annotationFilters.pos1Exclusions/pos2Exclusions while preserving runtime filtering behavior. Scope: remove config parsing/registry/docs/example for annotationFilters.pos1Exclusions/pos2Exclusions while preserving runtime filtering behavior.
<!-- SECTION:DESCRIPTION:END --> <!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria ## Acceptance Criteria
<!-- AC:BEGIN --> <!-- AC:BEGIN -->
- [x] #1 No user-facing config option exists for annotation POS exclusions. - [x] #1 No user-facing config option exists for annotation POS exclusions.
- [x] #2 Runtime N+1/frequency exclusion behavior remains active via built-in defaults. - [x] #2 Runtime N+1/frequency exclusion behavior remains active via built-in defaults.
- [x] #3 Config/docs/example/tests updated accordingly. - [x] #3 Config/docs/example/tests updated accordingly.
@@ -28,5 +33,7 @@ Scope: remove config parsing/registry/docs/example for annotationFilters.pos1Exc
## Final Summary ## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN --> <!-- SECTION:FINAL_SUMMARY:BEGIN -->
Removed user-facing subtitleStyle.annotationFilters POS exclusion configuration (schema/resolver/options/docs/example). POS-based N+1/frequency filtering now always uses built-in defaults in runtime. Preserved robust exclusion behavior including merged-token overlap POS handling and N+1-only MeCab enrichment path. Removed user-facing subtitleStyle.annotationFilters POS exclusion configuration (schema/resolver/options/docs/example). POS-based N+1/frequency filtering now always uses built-in defaults in runtime. Preserved robust exclusion behavior including merged-token overlap POS handling and N+1-only MeCab enrichment path.
<!-- SECTION:FINAL_SUMMARY:END --> <!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -273,8 +273,8 @@ When you mine the same word multiple times, SubMiner can merge the cards instead
### What Gets Merged ### What Gets Merged
| Field | Merge behavior | | Field | Merge behavior |
| -------- | -------------------------------------------------------------- | | -------- | --------------------------------------------------------------- |
| Sentence | Both sentences preserved (exact duplicate text is deduplicated) | | Sentence | Both sentences preserved (exact duplicate text is deduplicated) |
| Audio | Both `[sound:...]` entries kept (exact duplicates deduplicated) | | Audio | Both `[sound:...]` entries kept (exact duplicates deduplicated) |
| Image | Both images kept (exact duplicates deduplicated) | | Image | Both images kept (exact duplicates deduplicated) |
@@ -299,7 +299,7 @@ When you mine the same word multiple times, SubMiner can merge the cards instead
"enabled": false, "enabled": false,
"host": "127.0.0.1", "host": "127.0.0.1",
"port": 8766, "port": 8766,
"upstreamUrl": "http://127.0.0.1:8765" "upstreamUrl": "http://127.0.0.1:8765",
}, },
"fields": { "fields": {
"audio": "ExpressionAudio", "audio": "ExpressionAudio",

View File

@@ -172,11 +172,11 @@ This example is intentionally compact. The option table below documents availabl
| --------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | --------------------------------------- | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `enabled` | `true`, `false` | Enable AnkiConnect integration (default: `false`) | | `enabled` | `true`, `false` | Enable AnkiConnect integration (default: `false`) |
| `url` | string (URL) | AnkiConnect API URL (default: `http://127.0.0.1:8765`) | | `url` | string (URL) | AnkiConnect API URL (default: `http://127.0.0.1:8765`) |
| `pollingRate` | number (ms) | How often to check for new cards in polling mode (default: `3000`; ignored for direct proxy `addNote`/`addNotes` updates) | | `pollingRate` | number (ms) | How often to check for new cards in polling mode (default: `3000`; ignored for direct proxy `addNote`/`addNotes` updates) |
| `proxy.enabled` | `true`, `false` | Enable local AnkiConnect-compatible proxy for push-based auto-enrichment (default: `true`) | | `proxy.enabled` | `true`, `false` | Enable local AnkiConnect-compatible proxy for push-based auto-enrichment (default: `true`) |
| `proxy.host` | string | Bind host for local AnkiConnect proxy (default: `127.0.0.1`) | | `proxy.host` | string | Bind host for local AnkiConnect proxy (default: `127.0.0.1`) |
| `proxy.port` | number | Bind port for local AnkiConnect proxy (default: `8766`) | | `proxy.port` | number | Bind port for local AnkiConnect proxy (default: `8766`) |
| `proxy.upstreamUrl` | string (URL) | Upstream AnkiConnect URL that proxy forwards to (default: `http://127.0.0.1:8765`) | | `proxy.upstreamUrl` | string (URL) | Upstream AnkiConnect URL that proxy forwards to (default: `http://127.0.0.1:8765`) |
| `tags` | array of strings | Tags automatically added to cards mined/updated by SubMiner (default: `['SubMiner']`; set `[]` to disable automatic tagging). | | `tags` | array of strings | Tags automatically added to cards mined/updated by SubMiner (default: `['SubMiner']`; set `[]` to disable automatic tagging). |
| `deck` | string | Anki deck to monitor for new cards | | `deck` | string | Anki deck to monitor for new cards |
| `ankiConnect.nPlusOne.decks` | array of strings | Decks used for N+1 known-word cache lookups. When omitted/empty, falls back to `ankiConnect.deck`. | | `ankiConnect.nPlusOne.decks` | array of strings | Decks used for N+1 known-word cache lookups. When omitted/empty, falls back to `ankiConnect.deck`. |
@@ -499,6 +499,7 @@ Jellyfin integration is optional and disabled by default. When enabled, SubMiner
| `transcodeVideoCodec` | string | Preferred transcode video codec fallback (default: `h264`) | | `transcodeVideoCodec` | string | Preferred transcode video codec fallback (default: `h264`) |
Jellyfin auth session (`accessToken` + `userId`) is stored in local encrypted storage after login/setup. Jellyfin auth session (`accessToken` + `userId`) is stored in local encrypted storage after login/setup.
- On Linux, token storage defaults to `gnome-libsecret` for `safeStorage`. Override with `--password-store=<backend>` on launcher/app invocations when needed. - On Linux, token storage defaults to `gnome-libsecret` for `safeStorage`. Override with `--password-store=<backend>` on launcher/app invocations when needed.
Launcher subcommands: Launcher subcommands:
@@ -561,21 +562,21 @@ See `config.example.jsonc` for detailed configuration options and more examples.
**Default keybindings:** **Default keybindings:**
| Key | Command | Description | | Key | Command | Description |
| ----------------- | -------------------------- | ------------------------------------- | | ----------------- | ---------------------------- | ------------------------------------- |
| `Space` | `["cycle", "pause"]` | Toggle pause | | `Space` | `["cycle", "pause"]` | Toggle pause |
| `KeyJ` | `["cycle", "sid"]` | Cycle primary subtitle track | | `KeyJ` | `["cycle", "sid"]` | Cycle primary subtitle track |
| `Shift+KeyJ` | `["cycle", "secondary-sid"]` | Cycle secondary subtitle track | | `Shift+KeyJ` | `["cycle", "secondary-sid"]` | Cycle secondary subtitle track |
| `ArrowRight` | `["seek", 5]` | Seek forward 5 seconds | | `ArrowRight` | `["seek", 5]` | Seek forward 5 seconds |
| `ArrowLeft` | `["seek", -5]` | Seek backward 5 seconds | | `ArrowLeft` | `["seek", -5]` | Seek backward 5 seconds |
| `ArrowUp` | `["seek", 60]` | Seek forward 60 seconds | | `ArrowUp` | `["seek", 60]` | Seek forward 60 seconds |
| `ArrowDown` | `["seek", -60]` | Seek backward 60 seconds | | `ArrowDown` | `["seek", -60]` | Seek backward 60 seconds |
| `Shift+KeyH` | `["sub-seek", -1]` | Jump to previous subtitle | | `Shift+KeyH` | `["sub-seek", -1]` | Jump to previous subtitle |
| `Shift+KeyL` | `["sub-seek", 1]` | Jump to next subtitle | | `Shift+KeyL` | `["sub-seek", 1]` | Jump to next subtitle |
| `Ctrl+Shift+KeyH` | `["__replay-subtitle"]` | Replay current subtitle, pause at end | | `Ctrl+Shift+KeyH` | `["__replay-subtitle"]` | Replay current subtitle, pause at end |
| `Ctrl+Shift+KeyL` | `["__play-next-subtitle"]` | Play next subtitle, pause at end | | `Ctrl+Shift+KeyL` | `["__play-next-subtitle"]` | Play next subtitle, pause at end |
| `KeyQ` | `["quit"]` | Quit mpv | | `KeyQ` | `["quit"]` | Quit mpv |
| `Ctrl+KeyW` | `["quit"]` | Quit mpv | | `Ctrl+KeyW` | `["quit"]` | Quit mpv |
**Custom keybindings example:** **Custom keybindings example:**
@@ -614,8 +615,14 @@ Use the runtime options palette to toggle settings live while SubMiner is runnin
Current runtime options: Current runtime options:
- `ankiConnect.behavior.autoUpdateNewCards` (`On` / `Off`) - `ankiConnect.behavior.autoUpdateNewCards` (`On` / `Off`)
- `ankiConnect.nPlusOne.highlightEnabled` (`On` / `Off`)
- `subtitleStyle.enableJlpt` (`On` / `Off`)
- `subtitleStyle.frequencyDictionary.enabled` (`On` / `Off`)
- `ankiConnect.nPlusOne.matchMode` (`headword` / `surface`)
- `ankiConnect.isKiku.fieldGrouping` (`auto` / `manual` / `disabled`) - `ankiConnect.isKiku.fieldGrouping` (`auto` / `manual` / `disabled`)
Annotation toggles (`nPlusOne`, `enableJlpt`, `frequencyDictionary.enabled`) only apply to new subtitle lines after the toggle. The currently displayed line is not re-tokenized in place.
Default shortcut: `Ctrl+Shift+O` Default shortcut: `Ctrl+Shift+O`
Palette controls: Palette controls:
@@ -680,21 +687,21 @@ See `config.example.jsonc` for detailed configuration options.
} }
``` ```
| Option | Values | Description | | Option | Values | Description |
| ------------------------------ | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | ----------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------- |
| `toggleVisibleOverlayGlobal` | string \| `null` | Global accelerator for toggling visible subtitle overlay (default: `"Alt+Shift+O"`) | | `toggleVisibleOverlayGlobal` | string \| `null` | Global accelerator for toggling visible subtitle overlay (default: `"Alt+Shift+O"`) |
| `copySubtitle` | string \| `null` | Accelerator for copying current subtitle (default: `"CommandOrControl+C"`) | | `copySubtitle` | string \| `null` | Accelerator for copying current subtitle (default: `"CommandOrControl+C"`) |
| `copySubtitleMultiple` | string \| `null` | Accelerator for multi-copy mode (default: `"CommandOrControl+Shift+C"`) | | `copySubtitleMultiple` | string \| `null` | Accelerator for multi-copy mode (default: `"CommandOrControl+Shift+C"`) |
| `updateLastCardFromClipboard` | string \| `null` | Accelerator for updating card from clipboard (default: `"CommandOrControl+V"`) | | `updateLastCardFromClipboard` | string \| `null` | Accelerator for updating card from clipboard (default: `"CommandOrControl+V"`) |
| `triggerFieldGrouping` | string \| `null` | Accelerator for Kiku field grouping on last card (default: `"CommandOrControl+G"`; only active when `behavior.autoUpdateNewCards` is `false`) | | `triggerFieldGrouping` | string \| `null` | Accelerator for Kiku field grouping on last card (default: `"CommandOrControl+G"`; only active when `behavior.autoUpdateNewCards` is `false`) |
| `triggerSubsync` | string \| `null` | Accelerator for running Subsync (default: `"Ctrl+Alt+S"`) | | `triggerSubsync` | string \| `null` | Accelerator for running Subsync (default: `"Ctrl+Alt+S"`) |
| `mineSentence` | string \| `null` | Accelerator for creating sentence card from current subtitle (default: `"CommandOrControl+S"`) | | `mineSentence` | string \| `null` | Accelerator for creating sentence card from current subtitle (default: `"CommandOrControl+S"`) |
| `mineSentenceMultiple` | string \| `null` | Accelerator for multi-mine sentence card mode (default: `"CommandOrControl+Shift+S"`) | | `mineSentenceMultiple` | string \| `null` | Accelerator for multi-mine sentence card mode (default: `"CommandOrControl+Shift+S"`) |
| `multiCopyTimeoutMs` | number | Timeout in ms for multi-copy/mine digit input (default: `3000`) | | `multiCopyTimeoutMs` | number | Timeout in ms for multi-copy/mine digit input (default: `3000`) |
| `toggleSecondarySub` | string \| `null` | Accelerator for cycling secondary subtitle mode (default: `"CommandOrControl+Shift+V"`) | | `toggleSecondarySub` | string \| `null` | Accelerator for cycling secondary subtitle mode (default: `"CommandOrControl+Shift+V"`) |
| `markAudioCard` | string \| `null` | Accelerator for marking last card as audio card (default: `"CommandOrControl+Shift+A"`) | | `markAudioCard` | string \| `null` | Accelerator for marking last card as audio card (default: `"CommandOrControl+Shift+A"`) |
| `openRuntimeOptions` | string \| `null` | Opens runtime options palette for live session-only toggles (default: `"CommandOrControl+Shift+O"`) | | `openRuntimeOptions` | string \| `null` | Opens runtime options palette for live session-only toggles (default: `"CommandOrControl+Shift+O"`) |
| `openJimaku` | string \| `null` | Opens the Jimaku search modal (default: `"Ctrl+Shift+J"`) | | `openJimaku` | string \| `null` | Opens the Jimaku search modal (default: `"Ctrl+Shift+J"`) |
**See `config.example.jsonc`** for the complete list of shortcut configuration options. **See `config.example.jsonc`** for the complete list of shortcut configuration options.
@@ -750,27 +757,27 @@ See `config.example.jsonc` for detailed configuration options.
} }
``` ```
| Option | Values | Description | | Option | Values | Description |
| ---------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------- | | ---------------------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------- |
| `fontFamily` | string | CSS font-family value (default: `"M PLUS 1 Medium, Source Han Sans JP, Noto Sans CJK JP"`) | | `fontFamily` | string | CSS font-family value (default: `"M PLUS 1 Medium, Source Han Sans JP, Noto Sans CJK JP"`) |
| `fontSize` | number (px) | Font size in pixels (default: `35`) | | `fontSize` | number (px) | Font size in pixels (default: `35`) |
| `fontColor` | string | Any CSS color value (default: `"#cad3f5"`) | | `fontColor` | string | Any CSS color value (default: `"#cad3f5"`) |
| `fontWeight` | string | CSS font-weight, e.g. `"bold"`, `"normal"`, `"600"` (default: `"600"`) | | `fontWeight` | string | CSS font-weight, e.g. `"bold"`, `"normal"`, `"600"` (default: `"600"`) |
| `fontStyle` | string | `"normal"` or `"italic"` (default: `"normal"`) | | `fontStyle` | string | `"normal"` or `"italic"` (default: `"normal"`) |
| `backgroundColor` | string | Any CSS color, including `"transparent"` (default: `"rgb(30, 32, 48, 0.88)"`) | | `backgroundColor` | string | Any CSS color, including `"transparent"` (default: `"rgb(30, 32, 48, 0.88)"`) |
| `enableJlpt` | boolean | Enable JLPT level underline styling (`false` by default) | | `enableJlpt` | boolean | Enable JLPT level underline styling (`false` by default) |
| `preserveLineBreaks` | boolean | Preserve line breaks in visible overlay subtitle rendering (`false` by default). Enable to mirror mpv line layout. | | `preserveLineBreaks` | boolean | Preserve line breaks in visible overlay subtitle rendering (`false` by default). Enable to mirror mpv line layout. |
| `frequencyDictionary.enabled` | boolean | Enable frequency highlighting from dictionary lookups (`false` by default) | | `frequencyDictionary.enabled` | boolean | Enable frequency highlighting from dictionary lookups (`false` by default) |
| `frequencyDictionary.sourcePath` | string | Path to a local frequency dictionary root. Leave empty or omit to use installed/default frequency-dictionary search paths. | | `frequencyDictionary.sourcePath` | string | Path to a local frequency dictionary root. Leave empty or omit to use installed/default frequency-dictionary search paths. |
| `frequencyDictionary.topX` | number | Only color tokens whose frequency rank is `<= topX` (`1000` by default) | | `frequencyDictionary.topX` | number | Only color tokens whose frequency rank is `<= topX` (`1000` by default) |
| `frequencyDictionary.mode` | string | `"single"` or `"banded"` (`"single"` by default) | | `frequencyDictionary.mode` | string | `"single"` or `"banded"` (`"single"` by default) |
| `frequencyDictionary.matchMode` | string | `"headword"` or `"surface"` (`"headword"` by default) | | `frequencyDictionary.matchMode` | string | `"headword"` or `"surface"` (`"headword"` by default) |
| `frequencyDictionary.singleColor` | string | Color used for all highlighted tokens in single mode | | `frequencyDictionary.singleColor` | string | Color used for all highlighted tokens in single mode |
| `frequencyDictionary.bandedColors` | string[] | Array of five hex colors used for ranked bands in banded mode | | `frequencyDictionary.bandedColors` | string[] | Array of five hex colors used for ranked bands in banded mode |
| `nPlusOneColor` | string | Existing n+1 highlight color (default: `#c6a0f6`) | | `nPlusOneColor` | string | Existing n+1 highlight color (default: `#c6a0f6`) |
| `knownWordColor` | string | Existing known-word highlight color (default: `#a6da95`) | | `knownWordColor` | string | Existing known-word highlight color (default: `#a6da95`) |
| `jlptColors` | object | JLPT level underline colors object (`N1`..`N5`) | | `jlptColors` | object | JLPT level underline colors object (`N1`..`N5`) |
| `secondary` | object | Override any of the above for secondary subtitles (optional) | | `secondary` | object | Override any of the above for secondary subtitles (optional) |
JLPT underlining is powered by offline term-meta bank files at runtime. See [`docs/jlpt-vocab-bundle.md`](jlpt-vocab-bundle.md) for required files, source/version refresh steps, and deterministic fallback behavior. JLPT underlining is powered by offline term-meta bank files at runtime. See [`docs/jlpt-vocab-bundle.md`](jlpt-vocab-bundle.md) for required files, source/version refresh steps, and deterministic fallback behavior.
@@ -852,13 +859,13 @@ Control which startup warmups run in the background versus deferring to first re
} }
``` ```
| Option | Values | Description | | Option | Values | Description |
| ------------------------ | --------------- | ------------------------------------------------------------------------------------------------ | | ----------------------- | --------------- | ------------------------------------------------------------------------------------------------- |
| `lowPowerMode` | `true`, `false` | Defer all warmups except Yomitan extension | | `lowPowerMode` | `true`, `false` | Defer all warmups except Yomitan extension |
| `mecab` | `true`, `false` | Warm up MeCab tokenizer at startup | | `mecab` | `true`, `false` | Warm up MeCab tokenizer at startup |
| `yomitanExtension` | `true`, `false` | Warm up Yomitan extension at startup | | `yomitanExtension` | `true`, `false` | Warm up Yomitan extension at startup |
| `subtitleDictionaries` | `true`, `false` | Warm up JLPT + frequency dictionaries at startup | | `subtitleDictionaries` | `true`, `false` | Warm up JLPT + frequency dictionaries at startup |
| `jellyfinRemoteSession` | `true`, `false` | Warm up Jellyfin remote session at startup (still requires Jellyfin remote auto-connect settings) | | `jellyfinRemoteSession` | `true`, `false` | Warm up Jellyfin remote session at startup (still requires Jellyfin remote auto-connect settings) |
Defaults warm everything (`true` for all toggles, `lowPowerMode: false`). Setting a warmup toggle to `false` defers that work until first usage. Defaults warm everything (`true` for all toggles, `lowPowerMode: false`). Setting a warmup toggle to `false` defers that work until first usage.

View File

@@ -147,4 +147,3 @@ FROM imm_monthly_rollups
ORDER BY rollup_month DESC, video_id DESC ORDER BY rollup_month DESC, video_id DESC
LIMIT ?; LIMIT ?;
``` ```

View File

@@ -6,9 +6,19 @@ const docsIndexContents = readFileSync(docsIndexPath, 'utf8');
test('docs demo media uses shared cache-busting asset version token', () => { test('docs demo media uses shared cache-busting asset version token', () => {
expect(docsIndexContents).toMatch(/const demoAssetVersion = ['"][^'"]+['"]/); expect(docsIndexContents).toMatch(/const demoAssetVersion = ['"][^'"]+['"]/);
expect(docsIndexContents).toContain(':poster="`/assets/minecard-poster.jpg?v=${demoAssetVersion}`"'); expect(docsIndexContents).toContain(
expect(docsIndexContents).toContain('<source :src="`/assets/minecard.webm?v=${demoAssetVersion}`" type="video/webm" />'); ':poster="`/assets/minecard-poster.jpg?v=${demoAssetVersion}`"',
expect(docsIndexContents).toContain('<source :src="`/assets/minecard.mp4?v=${demoAssetVersion}`" type="video/mp4" />'); );
expect(docsIndexContents).toContain('<a :href="`/assets/minecard.webm?v=${demoAssetVersion}`" target="_blank" rel="noreferrer">'); expect(docsIndexContents).toContain(
expect(docsIndexContents).toContain('<img :src="`/assets/minecard.gif?v=${demoAssetVersion}`" alt="SubMiner demo GIF fallback" style="width: 100%; height: auto;" />'); '<source :src="`/assets/minecard.webm?v=${demoAssetVersion}`" type="video/webm" />',
);
expect(docsIndexContents).toContain(
'<source :src="`/assets/minecard.mp4?v=${demoAssetVersion}`" type="video/mp4" />',
);
expect(docsIndexContents).toContain(
'<a :href="`/assets/minecard.webm?v=${demoAssetVersion}`" target="_blank" rel="noreferrer">',
);
expect(docsIndexContents).toContain(
'<img :src="`/assets/minecard.gif?v=${demoAssetVersion}`" alt="SubMiner demo GIF fallback" style="width: 100%; height: auto;" />',
);
}); });

View File

@@ -16,10 +16,10 @@ subminer -r -d ~/Anime # recursive search
fzf shows video files in a fuzzy-searchable list. If `chafa` is installed, you get thumbnail previews in the right pane. Thumbnails are sourced from the freedesktop thumbnail cache first, then generated on the fly with `ffmpegthumbnailer` or `ffmpeg` as fallback. fzf shows video files in a fuzzy-searchable list. If `chafa` is installed, you get thumbnail previews in the right pane. Thumbnails are sourced from the freedesktop thumbnail cache first, then generated on the fly with `ffmpegthumbnailer` or `ffmpeg` as fallback.
| Optional tool | Purpose | | Optional tool | Purpose |
| --------------------- | -------------------------------- | | ------------------- | --------------------------------- |
| `chafa` | Render thumbnails in the terminal | | `chafa` | Render thumbnails in the terminal |
| `ffmpegthumbnailer` | Generate thumbnails on the fly | | `ffmpegthumbnailer` | Generate thumbnails on the fly |
### rofi ### rofi
@@ -61,38 +61,37 @@ subminer ytsearch:"jp news" # YouTube search
## Subcommands ## Subcommands
| Subcommand | Purpose | | Subcommand | Purpose |
| ------------------------- | ---------------------------------------------- | | -------------------------- | ---------------------------------------------------------- |
| `subminer jellyfin` / `jf` | Jellyfin workflows (`-d` discovery, `-p` play, `-l` login) | | `subminer jellyfin` / `jf` | Jellyfin workflows (`-d` discovery, `-p` play, `-l` login) |
| `subminer yt` / `youtube` | YouTube shorthand (`-o`, `-m`) | | `subminer yt` / `youtube` | YouTube shorthand (`-o`, `-m`) |
| `subminer doctor` | Dependency + config + socket diagnostics | | `subminer doctor` | Dependency + config + socket diagnostics |
| `subminer config path` | Print active config file path | | `subminer config path` | Print active config file path |
| `subminer config show` | Print active config contents | | `subminer config show` | Print active config contents |
| `subminer mpv status` | Check mpv socket readiness | | `subminer mpv status` | Check mpv socket readiness |
| `subminer mpv socket` | Print active socket path | | `subminer mpv socket` | Print active socket path |
| `subminer mpv idle` | Launch detached idle mpv instance | | `subminer mpv idle` | Launch detached idle mpv instance |
| `subminer texthooker` | Launch texthooker-only mode | | `subminer texthooker` | Launch texthooker-only mode |
| `subminer app` | Pass arguments directly to SubMiner binary | | `subminer app` | Pass arguments directly to SubMiner binary |
Use `subminer <subcommand> -h` for command-specific help. Use `subminer <subcommand> -h` for command-specific help.
## Options ## Options
| Flag | Description | | Flag | Description |
| -------------------- | -------------------------------------------- | | --------------------- | --------------------------------------------------- |
| `-d, --directory` | Video search directory (default: cwd) | | `-d, --directory` | Video search directory (default: cwd) |
| `-r, --recursive` | Search directories recursively | | `-r, --recursive` | Search directories recursively |
| `-R, --rofi` | Use rofi instead of fzf | | `-R, --rofi` | Use rofi instead of fzf |
| `-S, --start` | Start overlay after mpv launches | | `-S, --start` | Start overlay after mpv launches |
| `-T, --no-texthooker`| Disable texthooker server | | `-T, --no-texthooker` | Disable texthooker server |
| `-p, --profile` | mpv profile name (default: `subminer`) | | `-p, --profile` | mpv profile name (default: `subminer`) |
| `-b, --backend` | Force window backend (`hyprland`, `sway`, `x11`) | | `-b, --backend` | Force window backend (`hyprland`, `sway`, `x11`) |
| `--log-level` | Logger verbosity (`debug`, `info`, `warn`, `error`) | | `--log-level` | Logger verbosity (`debug`, `info`, `warn`, `error`) |
| `--dev`, `--debug` | Enable app dev-mode (not tied to log level) | | `--dev`, `--debug` | Enable app dev-mode (not tied to log level) |
## Logging ## Logging
- Default log level is `info` - Default log level is `info`
- `--background` mode defaults to `warn` unless `--log-level` is explicitly set - `--background` mode defaults to `warn` unless `--log-level` is explicitly set
- `--dev` / `--debug` control app behavior, not logging verbosity — use `--log-level` for that - `--dev` / `--debug` control app behavior, not logging verbosity — use `--log-level` for that

View File

@@ -99,11 +99,11 @@ If you prefer a hands-on approach (animecards-style), you can copy the current s
This is useful when auto-update is disabled or when you want explicit control over which subtitle line gets attached to the card. This is useful when auto-update is disabled or when you want explicit control over which subtitle line gets attached to the card.
| Shortcut | Action | Config key | | Shortcut | Action | Config key |
| --------------------------- | ----------------------------------------- | ------------------------------------- | | -------------------------- | ------------------------------- | --------------------------------------- |
| `Ctrl/Cmd+C` | Copy current subtitle | `shortcuts.copySubtitle` | | `Ctrl/Cmd+C` | Copy current subtitle | `shortcuts.copySubtitle` |
| `Ctrl/Cmd+Shift+C` + digit | Copy multiple recent lines | `shortcuts.copySubtitleMultiple` | | `Ctrl/Cmd+Shift+C` + digit | Copy multiple recent lines | `shortcuts.copySubtitleMultiple` |
| `Ctrl/Cmd+V` | Update last card from clipboard | `shortcuts.updateLastCardFromClipboard` | | `Ctrl/Cmd+V` | Update last card from clipboard | `shortcuts.updateLastCardFromClipboard` |
### 3. Mine Sentence (Hotkey) ### 3. Mine Sentence (Hotkey)

View File

@@ -29,16 +29,16 @@ input-ipc-server=/tmp/subminer-socket
All keybindings use a `y` chord prefix — press `y`, then the second key: All keybindings use a `y` chord prefix — press `y`, then the second key:
| Chord | Action | | Chord | Action |
| ----- | ------------------------ | | ----- | ---------------------- |
| `y-y` | Open menu | | `y-y` | Open menu |
| `y-s` | Start overlay | | `y-s` | Start overlay |
| `y-S` | Stop overlay | | `y-S` | Stop overlay |
| `y-t` | Toggle visible overlay | | `y-t` | Toggle visible overlay |
| `y-o` | Open settings window | | `y-o` | Open settings window |
| `y-r` | Restart overlay | | `y-r` | Restart overlay |
| `y-c` | Check status | | `y-c` | Check status |
| `y-k` | Skip intro (AniSkip) | | `y-k` | Skip intro (AniSkip) |
## Menu ## Menu
@@ -116,26 +116,26 @@ aniskip_button_duration=3
### Option Reference ### Option Reference
| Option | Default | Values | Description | | Option | Default | Values | Description |
| ------------------------------ | ---------------------- | ------------------------------------------ | -------------------------------- | | ---------------------------- | ----------------------------- | ------------------------------------------ | ---------------------------------------------------------------------- |
| `binary_path` | `""` (auto-detect) | file path | Path to SubMiner binary | | `binary_path` | `""` (auto-detect) | file path | Path to SubMiner binary |
| `socket_path` | `/tmp/subminer-socket` | file path | MPV IPC socket path | | `socket_path` | `/tmp/subminer-socket` | file path | MPV IPC socket path |
| `texthooker_enabled` | `yes` | `yes` / `no` | Enable texthooker server | | `texthooker_enabled` | `yes` | `yes` / `no` | Enable texthooker server |
| `texthooker_port` | `5174` | 165535 | Texthooker server port | | `texthooker_port` | `5174` | 165535 | Texthooker server port |
| `backend` | `auto` | `auto`, `hyprland`, `sway`, `x11`, `macos` | Window manager backend | | `backend` | `auto` | `auto`, `hyprland`, `sway`, `x11`, `macos` | Window manager backend |
| `auto_start` | `no` | `yes` / `no` | Auto-start overlay on file load when mpv socket matches `socket_path` | | `auto_start` | `no` | `yes` / `no` | Auto-start overlay on file load when mpv socket matches `socket_path` |
| `auto_start_visible_overlay` | `no` | `yes` / `no` | Show visible layer on auto-start when mpv socket matches `socket_path` | | `auto_start_visible_overlay` | `no` | `yes` / `no` | Show visible layer on auto-start when mpv socket matches `socket_path` |
| `osd_messages` | `yes` | `yes` / `no` | Show OSD status messages | | `osd_messages` | `yes` | `yes` / `no` | Show OSD status messages |
| `log_level` | `info` | `debug`, `info`, `warn`, `error` | Log verbosity | | `log_level` | `info` | `debug`, `info`, `warn`, `error` | Log verbosity |
| `aniskip_enabled` | `yes` | `yes` / `no` | Enable AniSkip intro detection | | `aniskip_enabled` | `yes` | `yes` / `no` | Enable AniSkip intro detection |
| `aniskip_title` | `""` | string | Override title used for lookup | | `aniskip_title` | `""` | string | Override title used for lookup |
| `aniskip_season` | `""` | numeric season | Optional season hint | | `aniskip_season` | `""` | numeric season | Optional season hint |
| `aniskip_mal_id` | `""` | numeric MAL id | Skip title lookup; use fixed id | | `aniskip_mal_id` | `""` | numeric MAL id | Skip title lookup; use fixed id |
| `aniskip_episode` | `""` | numeric episode | Skip episode parsing; use fixed | | `aniskip_episode` | `""` | numeric episode | Skip episode parsing; use fixed |
| `aniskip_show_button` | `yes` | `yes` / `no` | Show in-range intro skip prompt | | `aniskip_show_button` | `yes` | `yes` / `no` | Show in-range intro skip prompt |
| `aniskip_button_text` | `You can skip by pressing %s` | string | OSD prompt format (`%s`=key) | | `aniskip_button_text` | `You can skip by pressing %s` | string | OSD prompt format (`%s`=key) |
| `aniskip_button_key` | `y-k` | mpv key chord | Primary key for intro skip action (`y-k` always works as fallback) | | `aniskip_button_key` | `y-k` | mpv key chord | Primary key for intro skip action (`y-k` always works as fallback) |
| `aniskip_button_duration` | `3` | float seconds | OSD hint duration | | `aniskip_button_duration` | `3` | float seconds | OSD hint duration |
## Binary Auto-Detection ## Binary Auto-Detection

View File

@@ -13,10 +13,12 @@
### Task 1: Add Regression Tests For Main Overlay Secondary Rendering ### Task 1: Add Regression Tests For Main Overlay Secondary Rendering
**Files:** **Files:**
- Modify: `src/renderer/subtitle-render.test.ts` - Modify: `src/renderer/subtitle-render.test.ts`
- Modify: `src/renderer/error-recovery.test.ts` - Modify: `src/renderer/error-recovery.test.ts`
**Step 1: Write failing tests** **Step 1: Write failing tests**
- Assert stylesheet no longer hides secondary subtitles in `layer-visible`. - Assert stylesheet no longer hides secondary subtitles in `layer-visible`.
- Assert renderer platform resolution ignores legacy `secondary` overlay layer. - Assert renderer platform resolution ignores legacy `secondary` overlay layer.
@@ -28,12 +30,14 @@ Expected: FAIL on secondary subtitle hide rule + legacy secondary layer handling
### Task 2: Remove Secondary-Window CSS/Routing Assumptions ### Task 2: Remove Secondary-Window CSS/Routing Assumptions
**Files:** **Files:**
- Modify: `src/renderer/style.css` - Modify: `src/renderer/style.css`
- Modify: `src/renderer/utils/platform.ts` - Modify: `src/renderer/utils/platform.ts`
- Modify: `src/renderer/error-recovery.ts` - Modify: `src/renderer/error-recovery.ts`
- Modify: `src/types.ts` - Modify: `src/types.ts`
**Step 1: Implement minimal changes** **Step 1: Implement minimal changes**
- Remove legacy forced hide on `#secondarySubContainer`. - Remove legacy forced hide on `#secondarySubContainer`.
- Remove obsolete layer-specific secondary-subtitle CSS blocks. - Remove obsolete layer-specific secondary-subtitle CSS blocks.
- Drop legacy `secondary` overlay-layer parsing path from renderer platform resolver. - Drop legacy `secondary` overlay-layer parsing path from renderer platform resolver.
@@ -47,6 +51,7 @@ Expected: PASS.
### Task 3: Validate Wider Related Surface ### Task 3: Validate Wider Related Surface
**Files:** **Files:**
- No additional code changes required. - No additional code changes required.
**Step 1: Run broader related tests** **Step 1: Run broader related tests**

View File

@@ -5,7 +5,6 @@
* Copy to $XDG_CONFIG_HOME/SubMiner/config.jsonc (or ~/.config/SubMiner/config.jsonc) and edit as needed. * Copy to $XDG_CONFIG_HOME/SubMiner/config.jsonc (or ~/.config/SubMiner/config.jsonc) and edit as needed.
*/ */
{ {
// ========================================== // ==========================================
// Overlay Auto-Start // Overlay Auto-Start
// When overlay connects to mpv, automatically show overlay and hide mpv subtitles. // When overlay connects to mpv, automatically show overlay and hide mpv subtitles.
@@ -17,7 +16,7 @@
// Control whether browser opens automatically for texthooker. // Control whether browser opens automatically for texthooker.
// ========================================== // ==========================================
"texthooker": { "texthooker": {
"openBrowser": true // Open browser setting. Values: true | false "openBrowser": true, // Open browser setting. Values: true | false
}, // Control whether browser opens automatically for texthooker. }, // Control whether browser opens automatically for texthooker.
// ========================================== // ==========================================
@@ -27,7 +26,7 @@
// ========================================== // ==========================================
"websocket": { "websocket": {
"enabled": "auto", // Built-in subtitle websocket server mode. Values: auto | true | false "enabled": "auto", // Built-in subtitle websocket server mode. Values: auto | true | false
"port": 6677 // Built-in subtitle websocket server port. "port": 6677, // Built-in subtitle websocket server port.
}, // Built-in WebSocket server broadcasts subtitle text to connected clients. }, // Built-in WebSocket server broadcasts subtitle text to connected clients.
// ========================================== // ==========================================
@@ -36,7 +35,7 @@
// Set to debug for full runtime diagnostics. // Set to debug for full runtime diagnostics.
// ========================================== // ==========================================
"logging": { "logging": {
"level": "info" // Minimum log level for runtime logging. Values: debug | info | warn | error "level": "info", // Minimum log level for runtime logging. Values: debug | info | warn | error
}, // Controls logging verbosity. }, // Controls logging verbosity.
// ========================================== // ==========================================
@@ -57,7 +56,7 @@
"toggleSecondarySub": "CommandOrControl+Shift+V", // Toggle secondary sub setting. "toggleSecondarySub": "CommandOrControl+Shift+V", // Toggle secondary sub setting.
"markAudioCard": "CommandOrControl+Shift+A", // Mark audio card setting. "markAudioCard": "CommandOrControl+Shift+A", // Mark audio card setting.
"openRuntimeOptions": "CommandOrControl+Shift+O", // Open runtime options setting. "openRuntimeOptions": "CommandOrControl+Shift+O", // Open runtime options setting.
"openJimaku": "Ctrl+Shift+J" // Open jimaku setting. "openJimaku": "Ctrl+Shift+J", // Open jimaku setting.
}, // Overlay keyboard shortcuts. Set a shortcut to null to disable. }, // Overlay keyboard shortcuts. Set a shortcut to null to disable.
// ========================================== // ==========================================
@@ -77,7 +76,7 @@
"secondarySub": { "secondarySub": {
"secondarySubLanguages": [], // Secondary sub languages setting. "secondarySubLanguages": [], // Secondary sub languages setting.
"autoLoadSecondarySub": false, // Auto load secondary sub setting. Values: true | false "autoLoadSecondarySub": false, // Auto load secondary sub setting. Values: true | false
"defaultMode": "hover" // Default mode setting. "defaultMode": "hover", // Default mode setting.
}, // Dual subtitle track options. }, // Dual subtitle track options.
// ========================================== // ==========================================
@@ -88,7 +87,7 @@
"defaultMode": "auto", // Subsync default mode. Values: auto | manual "defaultMode": "auto", // Subsync default mode. Values: auto | manual
"alass_path": "", // Alass path setting. "alass_path": "", // Alass path setting.
"ffsubsync_path": "", // Ffsubsync path setting. "ffsubsync_path": "", // Ffsubsync path setting.
"ffmpeg_path": "" // Ffmpeg path setting. "ffmpeg_path": "", // Ffmpeg path setting.
}, // Subsync engine and executable paths. }, // Subsync engine and executable paths.
// ========================================== // ==========================================
@@ -96,7 +95,7 @@
// Initial vertical subtitle position from the bottom. // Initial vertical subtitle position from the bottom.
// ========================================== // ==========================================
"subtitlePosition": { "subtitlePosition": {
"yPercent": 10 // Y percent setting. "yPercent": 10, // Y percent setting.
}, // Initial vertical subtitle position from the bottom. }, // Initial vertical subtitle position from the bottom.
// ========================================== // ==========================================
@@ -129,7 +128,7 @@
"N2": "#f5a97f", // N2 setting. "N2": "#f5a97f", // N2 setting.
"N3": "#f9e2af", // N3 setting. "N3": "#f9e2af", // N3 setting.
"N4": "#a6e3a1", // N4 setting. "N4": "#a6e3a1", // N4 setting.
"N5": "#8aadf4" // N5 setting. "N5": "#8aadf4", // N5 setting.
}, // Jlpt colors setting. }, // Jlpt colors setting.
"frequencyDictionary": { "frequencyDictionary": {
"enabled": false, // Enable frequency-dictionary-based highlighting based on token rank. Values: true | false "enabled": false, // Enable frequency-dictionary-based highlighting based on token rank. Values: true | false
@@ -138,13 +137,7 @@
"mode": "single", // single: use one color for all matching tokens. banded: use color ramp by frequency band. Values: single | banded "mode": "single", // single: use one color for all matching tokens. banded: use color ramp by frequency band. Values: single | banded
"matchMode": "headword", // Frequency lookup text selection mode. Values: headword | surface "matchMode": "headword", // Frequency lookup text selection mode. Values: headword | surface
"singleColor": "#f5a97f", // Color used when frequencyDictionary.mode is `single`. "singleColor": "#f5a97f", // Color used when frequencyDictionary.mode is `single`.
"bandedColors": [ "bandedColors": ["#ed8796", "#f5a97f", "#f9e2af", "#a6e3a1", "#8aadf4"], // Five colors used for rank bands when mode is `banded` (from most common to least within topX).
"#ed8796",
"#f5a97f",
"#f9e2af",
"#a6e3a1",
"#8aadf4"
] // Five colors used for rank bands when mode is `banded` (from most common to least within topX).
}, // Frequency dictionary setting. }, // Frequency dictionary setting.
"secondary": { "secondary": {
"fontFamily": "Manrope, Inter", // Font family setting. "fontFamily": "Manrope, Inter", // Font family setting.
@@ -159,8 +152,8 @@
"backgroundColor": "transparent", // Background color setting. "backgroundColor": "transparent", // Background color setting.
"backdropFilter": "blur(6px)", // Backdrop filter setting. "backdropFilter": "blur(6px)", // Backdrop filter setting.
"fontWeight": "normal", // Font weight setting. "fontWeight": "normal", // Font weight setting.
"fontStyle": "normal" // Font style setting. "fontStyle": "normal", // Font style setting.
} // Secondary setting. }, // Secondary setting.
}, // Primary and secondary subtitle styling. }, // Primary and secondary subtitle styling.
// ========================================== // ==========================================
@@ -177,17 +170,15 @@
"enabled": true, // Enable local AnkiConnect-compatible proxy for push-based auto-enrichment. Values: true | false "enabled": true, // Enable local AnkiConnect-compatible proxy for push-based auto-enrichment. Values: true | false
"host": "127.0.0.1", // Bind host for local AnkiConnect proxy. "host": "127.0.0.1", // Bind host for local AnkiConnect proxy.
"port": 8766, // Bind port for local AnkiConnect proxy. "port": 8766, // Bind port for local AnkiConnect proxy.
"upstreamUrl": "http://127.0.0.1:8765" // Upstream AnkiConnect URL proxied by local AnkiConnect proxy. "upstreamUrl": "http://127.0.0.1:8765", // Upstream AnkiConnect URL proxied by local AnkiConnect proxy.
}, // Proxy setting. }, // Proxy setting.
"tags": [ "tags": ["SubMiner"], // Tags to add to cards mined or updated by SubMiner. Provide an empty array to disable automatic tagging.
"SubMiner"
], // Tags to add to cards mined or updated by SubMiner. Provide an empty array to disable automatic tagging.
"fields": { "fields": {
"audio": "ExpressionAudio", // Audio setting. "audio": "ExpressionAudio", // Audio setting.
"image": "Picture", // Image setting. "image": "Picture", // Image setting.
"sentence": "Sentence", // Sentence setting. "sentence": "Sentence", // Sentence setting.
"miscInfo": "MiscInfo", // Misc info setting. "miscInfo": "MiscInfo", // Misc info setting.
"translation": "SelectionText" // Translation setting. "translation": "SelectionText", // Translation setting.
}, // Fields setting. }, // Fields setting.
"ai": { "ai": {
"enabled": false, // Enabled setting. Values: true | false "enabled": false, // Enabled setting. Values: true | false
@@ -196,7 +187,7 @@
"model": "openai/gpt-4o-mini", // Model setting. "model": "openai/gpt-4o-mini", // Model setting.
"baseUrl": "https://openrouter.ai/api", // Base url setting. "baseUrl": "https://openrouter.ai/api", // Base url setting.
"targetLanguage": "English", // Target language setting. "targetLanguage": "English", // Target language setting.
"systemPrompt": "You are a translation engine. Return only the translated text with no explanations." // System prompt setting. "systemPrompt": "You are a translation engine. Return only the translated text with no explanations.", // System prompt setting.
}, // Ai setting. }, // Ai setting.
"media": { "media": {
"generateAudio": true, // Generate audio setting. Values: true | false "generateAudio": true, // Generate audio setting. Values: true | false
@@ -209,7 +200,7 @@
"animatedCrf": 35, // Animated crf setting. "animatedCrf": 35, // Animated crf setting.
"audioPadding": 0.5, // Audio padding setting. "audioPadding": 0.5, // Audio padding setting.
"fallbackDuration": 3, // Fallback duration setting. "fallbackDuration": 3, // Fallback duration setting.
"maxMediaDuration": 30 // Max media duration setting. "maxMediaDuration": 30, // Max media duration setting.
}, // Media setting. }, // Media setting.
"behavior": { "behavior": {
"overwriteAudio": true, // Overwrite audio setting. Values: true | false "overwriteAudio": true, // Overwrite audio setting. Values: true | false
@@ -217,7 +208,7 @@
"mediaInsertMode": "append", // Media insert mode setting. "mediaInsertMode": "append", // Media insert mode setting.
"highlightWord": true, // Highlight word setting. Values: true | false "highlightWord": true, // Highlight word setting. Values: true | false
"notificationType": "osd", // Notification type setting. "notificationType": "osd", // Notification type setting.
"autoUpdateNewCards": true // Automatically update newly added cards. Values: true | false "autoUpdateNewCards": true, // Automatically update newly added cards. Values: true | false
}, // Behavior setting. }, // Behavior setting.
"nPlusOne": { "nPlusOne": {
"highlightEnabled": false, // Enable fast local highlighting for words already known in Anki. Values: true | false "highlightEnabled": false, // Enable fast local highlighting for words already known in Anki. Values: true | false
@@ -226,20 +217,20 @@
"decks": [], // Decks used for N+1 known-word cache scope. Supports one or more deck names. "decks": [], // Decks used for N+1 known-word cache scope. Supports one or more deck names.
"minSentenceWords": 3, // Minimum sentence word count required for N+1 targeting (default: 3). "minSentenceWords": 3, // Minimum sentence word count required for N+1 targeting (default: 3).
"nPlusOne": "#c6a0f6", // Color used for the single N+1 target token highlight. "nPlusOne": "#c6a0f6", // Color used for the single N+1 target token highlight.
"knownWord": "#a6da95" // Color used for legacy known-word highlights. "knownWord": "#a6da95", // Color used for legacy known-word highlights.
}, // N plus one setting. }, // N plus one setting.
"metadata": { "metadata": {
"pattern": "[SubMiner] %f (%t)" // Pattern setting. "pattern": "[SubMiner] %f (%t)", // Pattern setting.
}, // Metadata setting. }, // Metadata setting.
"isLapis": { "isLapis": {
"enabled": false, // Enabled setting. Values: true | false "enabled": false, // Enabled setting. Values: true | false
"sentenceCardModel": "Japanese sentences" // Sentence card model setting. "sentenceCardModel": "Japanese sentences", // Sentence card model setting.
}, // Is lapis setting. }, // Is lapis setting.
"isKiku": { "isKiku": {
"enabled": false, // Enabled setting. Values: true | false "enabled": false, // Enabled setting. Values: true | false
"fieldGrouping": "disabled", // Kiku duplicate-card field grouping mode. Values: auto | manual | disabled "fieldGrouping": "disabled", // Kiku duplicate-card field grouping mode. Values: auto | manual | disabled
"deleteDuplicateInAuto": true // Delete duplicate in auto setting. Values: true | false "deleteDuplicateInAuto": true, // Delete duplicate in auto setting. Values: true | false
} // Is kiku setting. }, // Is kiku setting.
}, // Automatic Anki updates and media generation options. }, // Automatic Anki updates and media generation options.
// ========================================== // ==========================================
@@ -249,7 +240,7 @@
"jimaku": { "jimaku": {
"apiBaseUrl": "https://jimaku.cc", // Api base url setting. "apiBaseUrl": "https://jimaku.cc", // Api base url setting.
"languagePreference": "ja", // Preferred language used in Jimaku search. Values: ja | en | none "languagePreference": "ja", // Preferred language used in Jimaku search. Values: ja | en | none
"maxEntryResults": 10 // Maximum Jimaku search results returned. "maxEntryResults": 10, // Maximum Jimaku search results returned.
}, // Jimaku API configuration and defaults. }, // Jimaku API configuration and defaults.
// ========================================== // ==========================================
@@ -260,10 +251,7 @@
"mode": "automatic", // YouTube subtitle generation mode for the launcher script. Values: automatic | preprocess | off "mode": "automatic", // YouTube subtitle generation mode for the launcher script. Values: automatic | preprocess | off
"whisperBin": "", // Path to whisper.cpp CLI used as fallback transcription engine. "whisperBin": "", // Path to whisper.cpp CLI used as fallback transcription engine.
"whisperModel": "", // Path to whisper model used for fallback transcription. "whisperModel": "", // Path to whisper model used for fallback transcription.
"primarySubLanguages": [ "primarySubLanguages": ["ja", "jpn"], // Comma-separated primary subtitle language priority used by the launcher.
"ja",
"jpn"
] // Comma-separated primary subtitle language priority used by the launcher.
}, // Defaults for subminer YouTube subtitle extraction/transcription mode. }, // Defaults for subminer YouTube subtitle extraction/transcription mode.
// ========================================== // ==========================================
@@ -272,7 +260,7 @@
// ========================================== // ==========================================
"anilist": { "anilist": {
"enabled": false, // Enable AniList post-watch progress updates. Values: true | false "enabled": false, // Enable AniList post-watch progress updates. Values: true | false
"accessToken": "" // Optional explicit AniList access token override; leave empty to use locally stored token from setup. "accessToken": "", // Optional explicit AniList access token override; leave empty to use locally stored token from setup.
}, // Anilist API credentials and update behavior. }, // Anilist API credentials and update behavior.
// ========================================== // ==========================================
@@ -296,16 +284,8 @@
"pullPictures": false, // Enable Jellyfin poster/icon fetching for launcher menus. Values: true | false "pullPictures": false, // Enable Jellyfin poster/icon fetching for launcher menus. Values: true | false
"iconCacheDir": "/tmp/subminer-jellyfin-icons", // Directory used by launcher for cached Jellyfin poster icons. "iconCacheDir": "/tmp/subminer-jellyfin-icons", // Directory used by launcher for cached Jellyfin poster icons.
"directPlayPreferred": true, // Try direct play before server-managed transcoding when possible. Values: true | false "directPlayPreferred": true, // Try direct play before server-managed transcoding when possible. Values: true | false
"directPlayContainers": [ "directPlayContainers": ["mkv", "mp4", "webm", "mov", "flac", "mp3", "aac"], // Container allowlist for direct play decisions.
"mkv", "transcodeVideoCodec": "h264", // Preferred transcode video codec when direct play is unavailable.
"mp4",
"webm",
"mov",
"flac",
"mp3",
"aac"
], // Container allowlist for direct play decisions.
"transcodeVideoCodec": "h264" // Preferred transcode video codec when direct play is unavailable.
}, // Optional Jellyfin integration for auth, browsing, and playback launch. }, // Optional Jellyfin integration for auth, browsing, and playback launch.
// ========================================== // ==========================================
@@ -316,7 +296,7 @@
"discordPresence": { "discordPresence": {
"enabled": false, // Enable optional Discord Rich Presence updates. Values: true | false "enabled": false, // Enable optional Discord Rich Presence updates. Values: true | false
"updateIntervalMs": 3000, // Minimum interval between presence payload updates. "updateIntervalMs": 3000, // Minimum interval between presence payload updates.
"debounceMs": 750 // Debounce delay used to collapse bursty presence updates. "debounceMs": 750, // Debounce delay used to collapse bursty presence updates.
}, // Optional Discord Rich Presence activity card updates for current playback/study session. }, // Optional Discord Rich Presence activity card updates for current playback/study session.
// ========================================== // ==========================================
@@ -338,7 +318,7 @@
"telemetryDays": 30, // Telemetry retention window in days. "telemetryDays": 30, // Telemetry retention window in days.
"dailyRollupsDays": 365, // Daily rollup retention window in days. "dailyRollupsDays": 365, // Daily rollup retention window in days.
"monthlyRollupsDays": 1825, // Monthly rollup retention window in days. "monthlyRollupsDays": 1825, // Monthly rollup retention window in days.
"vacuumIntervalDays": 7 // Minimum days between VACUUM runs. "vacuumIntervalDays": 7, // Minimum days between VACUUM runs.
} // Retention setting. }, // Retention setting.
} // Enable/disable immersion tracking. }, // Enable/disable immersion tracking.
} }

View File

@@ -6,10 +6,10 @@ All shortcuts are configurable in `config.jsonc` under `shortcuts` and `keybindi
These work system-wide regardless of which window has focus. These work system-wide regardless of which window has focus.
| Shortcut | Action | Configurable | | Shortcut | Action | Configurable |
| ------------- | ------------------------ | ---------------------------------------- | | ------------- | ---------------------- | -------------------------------------- |
| `Alt+Shift+O` | Toggle visible overlay | `shortcuts.toggleVisibleOverlayGlobal` | | `Alt+Shift+O` | Toggle visible overlay | `shortcuts.toggleVisibleOverlayGlobal` |
| `Alt+Shift+Y` | Open Yomitan settings | Fixed (not configurable) | | `Alt+Shift+Y` | Open Yomitan settings | Fixed (not configurable) |
::: tip ::: tip
Global shortcuts are registered with the OS. If they conflict with another application, update them in `shortcuts` config and restart SubMiner. Global shortcuts are registered with the OS. If they conflict with another application, update them in `shortcuts` config and restart SubMiner.
@@ -81,15 +81,15 @@ Enter edit mode to fine-tune subtitle alignment.
When the mpv plugin is installed, all commands use a `y` chord prefix — press `y`, then the second key within 1 second. When the mpv plugin is installed, all commands use a `y` chord prefix — press `y`, then the second key within 1 second.
| Chord | Action | | Chord | Action |
| ----- | --------------------------------------- | | ----- | ------------------------ |
| `y-y` | Open SubMiner menu (OSD) | | `y-y` | Open SubMiner menu (OSD) |
| `y-s` | Start overlay | | `y-s` | Start overlay |
| `y-S` | Stop overlay | | `y-S` | Stop overlay |
| `y-t` | Toggle visible overlay | | `y-t` | Toggle visible overlay |
| `y-o` | Open Yomitan settings | | `y-o` | Open Yomitan settings |
| `y-r` | Restart overlay | | `y-r` | Restart overlay |
| `y-c` | Check overlay status | | `y-c` | Check overlay status |
When the overlay has focus, press `y` then `d` to toggle DevTools (debugging helper). When the overlay has focus, press `y` then `d` to toggle DevTools (debugging helper).

View File

@@ -2,9 +2,9 @@
There are two ways to use SubMiner — the `subminer` wrapper script or the mpv plugin: There are two ways to use SubMiner — the `subminer` wrapper script or the mpv plugin:
| Approach | Best For | | Approach | Best For |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **subminer script** | All-in-one solution. Handles video selection, launches MPV with the correct socket, and manages app commands. Overlay start is explicit (`--start`, `-S`, or `y-s`). | | **subminer script** | All-in-one solution. Handles video selection, launches MPV with the correct socket, and manages app commands. Overlay start is explicit (`--start`, `-S`, or `y-s`). |
| **MPV plugin** | When you launch MPV yourself or from other tools. Provides in-MPV chord keybindings (e.g. `y-y` for menu) to control overlay visibility. Requires `--input-ipc-server=/tmp/subminer-socket`. | | **MPV plugin** | When you launch MPV yourself or from other tools. Provides in-MPV chord keybindings (e.g. `y-y` for menu) to control overlay visibility. Requires `--input-ipc-server=/tmp/subminer-socket`. |
You can use both together—install the plugin for on-demand control, but use `subminer` when you want the streamlined workflow. You can use both together—install the plugin for on-demand control, but use `subminer` when you want the streamlined workflow.
@@ -167,10 +167,10 @@ Notes:
### Global Shortcuts ### Global Shortcuts
| Keybind | Action | | Keybind | Action |
| ------------- | ------------------------ | | ------------- | ---------------------- |
| `Alt+Shift+O` | Toggle visible overlay | | `Alt+Shift+O` | Toggle visible overlay |
| `Alt+Shift+Y` | Open Yomitan settings | | `Alt+Shift+Y` | Open Yomitan settings |
`Alt+Shift+Y` is a fixed global shortcut; it is not part of `shortcuts` config. `Alt+Shift+Y` is a fixed global shortcut; it is not part of `shortcuts` config.