refactor(main): modularize runtime and harden anilist setup flow

This commit is contained in:
2026-02-19 16:04:59 -08:00
parent 58f28b7b55
commit 162be118e1
73 changed files with 4413 additions and 1251 deletions

View File

@@ -71,9 +71,11 @@ export default {
text: 'Reference',
items: [
{ text: 'Configuration', link: '/configuration' },
{ text: 'Immersion Tracking', link: '/immersion-tracking' },
{ text: 'Keyboard Shortcuts', link: '/shortcuts' },
{ text: 'Anki Integration', link: '/anki-integration' },
{ text: 'Jellyfin Integration', link: '/jellyfin-integration' },
{ text: 'Immersion Tracking', link: '/immersion-tracking' },
{ text: 'JLPT Vocabulary', link: '/jlpt-vocab-bundle' },
{ text: 'MPV Plugin', link: '/mpv-plugin' },
{ text: 'Troubleshooting', link: '/troubleshooting' },
],

View File

@@ -15,15 +15,17 @@ make docs-preview # Preview built site at http://localhost:4173
### Getting Started
- [Installation](/installation) — Requirements, Linux/macOS/Windows install, mpv plugin setup
- [Usage](/usage) — `subminer` wrapper + subcommands (`jellyfin`, `yt`, `doctor`, `config`, `mpv`, `texthooker`), mpv plugin, keybindings
- [Usage](/usage) — `subminer` wrapper + subcommands (`jellyfin`, `yt`, `doctor`, `config`, `mpv`, `texthooker`, `app`), mpv plugin, keybindings
- [Mining Workflow](/mining-workflow) — End-to-end sentence mining guide, overlay layers, card creation
### Reference
- [Configuration](/configuration) — Full config file reference and option details
- [Immersion Tracking](/immersion-tracking) — SQLite schema, retention/rollup policies, query templates, and extension points
- [Keyboard Shortcuts](/shortcuts) — All global, overlay, mining, and plugin chord shortcuts in one place
- [Anki Integration](/anki-integration) — AnkiConnect setup, field mapping, media generation, field grouping
- [Jellyfin Integration](/jellyfin-integration) — Optional Jellyfin auth, cast discovery, remote control, and playback launch
- [Immersion Tracking](/immersion-tracking) — SQLite schema, retention/rollup policies, query templates, and extension points
- [JLPT Vocabulary](/jlpt-vocab-bundle) — Bundled term-meta bank for JLPT level underlining and frequency highlighting
- [MPV Plugin](/mpv-plugin) — Chord keybindings, subminer.conf options, script messages
- [Troubleshooting](/troubleshooting) — Common issues and solutions by category

View File

@@ -413,13 +413,13 @@ Set `openBrowser` to `false` to only print the URL without opening a browser.
### AniList
AniList integration is opt-in and disabled by default. Enable it and provide an access token to allow SubMiner to update your watched episode progress after playback.
AniList integration is opt-in and disabled by default. Enable it to allow SubMiner to update watched episode progress after playback.
```json
{
"anilist": {
"enabled": true,
"accessToken": "YOUR_ANILIST_ACCESS_TOKEN"
"accessToken": ""
}
}
```
@@ -427,7 +427,7 @@ AniList integration is opt-in and disabled by default. Enable it and provide an
| Option | Values | Description |
| ------------- | --------------- | ----------------------------------------------------------------------------------- |
| `enabled` | `true`, `false` | Enable AniList post-watch progress updates (default: `false`) |
| `accessToken` | string | AniList access token used for authenticated GraphQL updates (default: empty string) |
| `accessToken` | string | Optional explicit AniList access token override (default: empty string) |
When `enabled` is `true` and `accessToken` is empty, SubMiner opens an AniList setup helper window. Keep `enabled` as `false` to disable all AniList setup/update behavior.
@@ -442,14 +442,13 @@ Current post-watch behavior:
Setup flow details:
1. Set `anilist.enabled` to `true`.
2. Leave `anilist.accessToken` empty and restart SubMiner to trigger setup.
3. Approve access in AniList (browser window or system browser fallback).
4. Copy the returned token and paste it into `anilist.accessToken`.
5. Save config and restart SubMiner.
2. Leave `anilist.accessToken` empty and restart SubMiner (or run `--anilist-setup`) to trigger setup.
3. Approve access in AniList.
4. Callback flow returns to SubMiner via `subminer://anilist-setup?...`, and SubMiner stores the token automatically.
Token + detection notes:
- `anilist.accessToken` can be set directly in config; SubMiner also stores the token locally for reuse if config token is later blank.
- `anilist.accessToken` can be set directly in config; when blank, SubMiner uses the locally stored encrypted token from setup.
- Detection quality is best when `guessit` is installed and available on `PATH`.
- When `guessit` cannot parse or is missing, SubMiner falls back automatically to internal filename parsing.

21
docs/file-size-budgets.md Normal file
View File

@@ -0,0 +1,21 @@
# File Size Budgets
Purpose: keep large modules from becoming maintenance bottlenecks.
## Current Budget
- TypeScript source files in `src/` and `launcher/`
- Soft budget: `500` LOC
- Excludes generated bundle artifacts (for example `subminer`)
## Commands
- Warning mode (non-blocking): `bun run check:file-budgets`
- Strict mode (CI/local gate): `bun run check:file-budgets:strict`
- Custom limit: `bun run scripts/check-file-budgets.ts --limit 650`
## Policy
- If file exceeds budget, prefer extracting domain module(s) first.
- Keep composition/orchestration files focused on wiring.
- Do not hand-edit generated artifacts; refactor source modules.

View File

@@ -39,17 +39,17 @@ features:
src: /assets/dual-layer.svg
alt: Dual layer icon
title: Dual-Layer Subtitles
details: Interactive visible overlay plus an invisible layer aligned with mpv's own rendering for seamless click-through lookup.
details: Interactive visible overlay plus an invisible layer aligned with mpv's own rendering for seamless click-through lookup. Both are independently controllable.
- icon:
src: /assets/highlight.svg
alt: Highlight icon
title: N+1 Highlighting
details: Marks words you already know from your Anki deck so you can spot new vocabulary and identify N+1 sentences at a glance.
- icon:
src: /assets/texthooker.svg
alt: Texthooker icon
title: Texthooker & WebSocket
details: Built-in texthooker page that receives subtitles over WebSocket — use it as a clipboard inserter or connect external tools.
src: /assets/tokenization.svg
alt: Tokenization icon
title: Immersion Tracking
details: Every subtitle line, word, and mined card is logged to local SQLite. Daily and monthly rollups let you measure your progress over time.
- icon:
src: /assets/subtitle-download.svg
alt: Subtitle download icon
@@ -60,6 +60,11 @@ features:
alt: Keyboard icon
title: Keyboard-Driven
details: Mine sentences, copy subtitles, cycle display modes, and trigger field grouping — all from configurable shortcuts.
- icon:
src: /assets/texthooker.svg
alt: Texthooker icon
title: Texthooker & WebSocket
details: Built-in texthooker page that receives subtitles over WebSocket — use it as a clipboard inserter or connect external tools.
---
<style>
@@ -104,14 +109,20 @@ features:
.workflow-steps {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-columns: repeat(5, 1fr);
gap: 1px;
background: var(--vp-c-divider);
border-radius: 12px;
overflow: hidden;
}
@media (max-width: 768px) {
@media (max-width: 960px) {
.workflow-steps {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 640px) {
.workflow-steps {
grid-template-columns: 1fr;
}
@@ -158,7 +169,7 @@ features:
<div class="workflow-step">
<div class="step-number">02</div>
<div class="step-title">Look Up</div>
<div class="step-desc">Hover over a word in the subtitle overlay and hold Shift to trigger a Yomitan lookup.</div>
<div class="step-desc">Hover over a word in the subtitle overlay to trigger a Yomitan lookup — no browser or tab-switching needed.</div>
</div>
<div class="workflow-step">
<div class="step-number">03</div>
@@ -170,6 +181,11 @@ features:
<div class="step-title">Enrich</div>
<div class="step-desc">SubMiner fills in the sentence, audio clip, screenshot, and translation — no extra steps.</div>
</div>
<div class="workflow-step">
<div class="step-number">05</div>
<div class="step-title">Track</div>
<div class="step-desc">Every line seen and card mined is logged to local SQLite. Daily and monthly rollups let you measure immersion over time.</div>
</div>
</div>
</div>

View File

@@ -5,26 +5,27 @@
* Copy to $XDG_CONFIG_HOME/SubMiner/config.jsonc (or ~/.config/SubMiner/config.jsonc) and edit as needed.
*/
{
// ==========================================
// Overlay Auto-Start
// When overlay connects to mpv, automatically show overlay and hide mpv subtitles.
// ==========================================
"auto_start_overlay": false,
"auto_start_overlay": false, // When overlay connects to mpv, automatically show overlay and hide mpv subtitles. Values: true | false
// ==========================================
// Visible Overlay Subtitle Binding
// Control whether visible overlay toggles also toggle MPV subtitle visibility.
// When enabled, visible overlay hides MPV subtitles; when disabled, MPV subtitles are left unchanged.
// ==========================================
"bind_visible_overlay_to_mpv_sub_visibility": true,
"bind_visible_overlay_to_mpv_sub_visibility": true, // Link visible overlay toggles to MPV subtitle visibility (primary and secondary). Values: true | false
// ==========================================
// Texthooker Server
// Control whether browser opens automatically for texthooker.
// ==========================================
"texthooker": {
"openBrowser": true,
},
"openBrowser": true // Open browser setting. Values: true | false
}, // Control whether browser opens automatically for texthooker.
// ==========================================
// WebSocket Server
@@ -32,9 +33,9 @@
// Auto mode disables built-in server if mpv_websocket is detected.
// ==========================================
"websocket": {
"enabled": "auto",
"port": 6677,
},
"enabled": "auto", // Built-in subtitle websocket server mode. Values: auto | true | false
"port": 6677 // Built-in subtitle websocket server port.
}, // Built-in WebSocket server broadcasts subtitle text to connected clients.
// ==========================================
// Logging
@@ -42,8 +43,8 @@
// Set to debug for full runtime diagnostics.
// ==========================================
"logging": {
"level": "info",
},
"level": "info" // Minimum log level for runtime logging. Values: debug | info | warn | error
}, // Controls logging verbosity.
// ==========================================
// AnkiConnect Integration
@@ -52,93 +53,93 @@
// Most other AnkiConnect settings still require restart.
// ==========================================
"ankiConnect": {
"enabled": false,
"url": "http://127.0.0.1:8765",
"pollingRate": 3000,
"tags": ["SubMiner"],
"enabled": false, // Enable AnkiConnect integration. Values: true | false
"url": "http://127.0.0.1:8765", // Url setting.
"pollingRate": 3000, // Polling interval in milliseconds.
"tags": [
"SubMiner"
], // Tags to add to cards mined or updated by SubMiner. Provide an empty array to disable automatic tagging.
"fields": {
"audio": "ExpressionAudio",
"image": "Picture",
"sentence": "Sentence",
"miscInfo": "MiscInfo",
"translation": "SelectionText",
},
"audio": "ExpressionAudio", // Audio setting.
"image": "Picture", // Image setting.
"sentence": "Sentence", // Sentence setting.
"miscInfo": "MiscInfo", // Misc info setting.
"translation": "SelectionText" // Translation setting.
}, // Fields setting.
"ai": {
"enabled": false,
"alwaysUseAiTranslation": false,
"apiKey": "",
"model": "openai/gpt-4o-mini",
"baseUrl": "https://openrouter.ai/api",
"targetLanguage": "English",
"systemPrompt": "You are a translation engine. Return only the translated text with no explanations.",
},
"enabled": false, // Enabled setting. Values: true | false
"alwaysUseAiTranslation": false, // Always use ai translation setting. Values: true | false
"apiKey": "", // Api key setting.
"model": "openai/gpt-4o-mini", // Model setting.
"baseUrl": "https://openrouter.ai/api", // Base url setting.
"targetLanguage": "English", // Target language setting.
"systemPrompt": "You are a translation engine. Return only the translated text with no explanations." // System prompt setting.
}, // Ai setting.
"media": {
"generateAudio": true,
"generateImage": true,
"imageType": "static",
"imageFormat": "jpg",
"imageQuality": 92,
"animatedFps": 10,
"animatedMaxWidth": 640,
"animatedCrf": 35,
"audioPadding": 0.5,
"fallbackDuration": 3,
"maxMediaDuration": 30,
},
"generateAudio": true, // Generate audio setting. Values: true | false
"generateImage": true, // Generate image setting. Values: true | false
"imageType": "static", // Image type setting.
"imageFormat": "jpg", // Image format setting.
"imageQuality": 92, // Image quality setting.
"animatedFps": 10, // Animated fps setting.
"animatedMaxWidth": 640, // Animated max width setting.
"animatedCrf": 35, // Animated crf setting.
"audioPadding": 0.5, // Audio padding setting.
"fallbackDuration": 3, // Fallback duration setting.
"maxMediaDuration": 30 // Max media duration setting.
}, // Media setting.
"behavior": {
"overwriteAudio": true,
"overwriteImage": true,
"mediaInsertMode": "append",
"highlightWord": true,
"notificationType": "osd",
"autoUpdateNewCards": true,
},
"overwriteAudio": true, // Overwrite audio setting. Values: true | false
"overwriteImage": true, // Overwrite image setting. Values: true | false
"mediaInsertMode": "append", // Media insert mode setting.
"highlightWord": true, // Highlight word setting. Values: true | false
"notificationType": "osd", // Notification type setting.
"autoUpdateNewCards": true // Automatically update newly added cards. Values: true | false
}, // Behavior setting.
"nPlusOne": {
"highlightEnabled": false,
"refreshMinutes": 1440,
"matchMode": "headword",
"decks": [],
"minSentenceWords": 3,
"nPlusOne": "#c6a0f6",
"knownWord": "#a6da95",
},
"highlightEnabled": false, // Enable fast local highlighting for words already known in Anki. Values: true | false
"refreshMinutes": 1440, // Minutes between known-word cache refreshes.
"matchMode": "headword", // Known-word matching strategy for N+1 highlighting. Values: headword | surface
"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).
"nPlusOne": "#c6a0f6", // Color used for the single N+1 target token highlight.
"knownWord": "#a6da95" // Color used for legacy known-word highlights.
}, // N plus one setting.
"metadata": {
"pattern": "[SubMiner] %f (%t)",
},
"pattern": "[SubMiner] %f (%t)" // Pattern setting.
}, // Metadata setting.
"isLapis": {
"enabled": false,
"sentenceCardModel": "Japanese sentences",
},
"enabled": false, // Enabled setting. Values: true | false
"sentenceCardModel": "Japanese sentences" // Sentence card model setting.
}, // Is lapis setting.
"isKiku": {
"enabled": false,
"fieldGrouping": "disabled",
"deleteDuplicateInAuto": true,
},
},
"enabled": false, // Enabled setting. Values: true | false
"fieldGrouping": "disabled", // Kiku duplicate-card field grouping mode. Values: auto | manual | disabled
"deleteDuplicateInAuto": true // Delete duplicate in auto setting. Values: true | false
} // Is kiku setting.
}, // Automatic Anki updates and media generation options.
// ==========================================
// Keyboard Shortcuts
// Overlay keyboard shortcuts. Set a shortcut to null to disable.
// Fixed (non-configurable) overlay shortcuts:
// - Ctrl/Cmd+A: append clipboard video path to MPV playlist
// Hot-reload: shortcut changes apply live and update the session help modal on reopen.
// ==========================================
"shortcuts": {
"toggleVisibleOverlayGlobal": "Alt+Shift+O",
"toggleInvisibleOverlayGlobal": "Alt+Shift+I",
"copySubtitle": "CommandOrControl+C",
"copySubtitleMultiple": "CommandOrControl+Shift+C",
"updateLastCardFromClipboard": "CommandOrControl+V",
"triggerFieldGrouping": "CommandOrControl+G",
"triggerSubsync": "Ctrl+Alt+S",
"mineSentence": "CommandOrControl+S",
"mineSentenceMultiple": "CommandOrControl+Shift+S",
"multiCopyTimeoutMs": 3000,
"toggleSecondarySub": "CommandOrControl+Shift+V",
"markAudioCard": "CommandOrControl+Shift+A",
"openRuntimeOptions": "CommandOrControl+Shift+O",
"openJimaku": "Ctrl+Shift+J",
},
"toggleVisibleOverlayGlobal": "Alt+Shift+O", // Toggle visible overlay global setting.
"toggleInvisibleOverlayGlobal": "Alt+Shift+I", // Toggle invisible overlay global setting.
"copySubtitle": "CommandOrControl+C", // Copy subtitle setting.
"copySubtitleMultiple": "CommandOrControl+Shift+C", // Copy subtitle multiple setting.
"updateLastCardFromClipboard": "CommandOrControl+V", // Update last card from clipboard setting.
"triggerFieldGrouping": "CommandOrControl+G", // Trigger field grouping setting.
"triggerSubsync": "Ctrl+Alt+S", // Trigger subsync setting.
"mineSentence": "CommandOrControl+S", // Mine sentence setting.
"mineSentenceMultiple": "CommandOrControl+Shift+S", // Mine sentence multiple setting.
"multiCopyTimeoutMs": 3000, // Timeout for multi-copy/mine modes.
"toggleSecondarySub": "CommandOrControl+Shift+V", // Toggle secondary sub setting.
"markAudioCard": "CommandOrControl+Shift+A", // Mark audio card setting.
"openRuntimeOptions": "CommandOrControl+Shift+O", // Open runtime options setting.
"openJimaku": "Ctrl+Shift+J" // Open jimaku setting.
}, // Overlay keyboard shortcuts. Set a shortcut to null to disable.
// ==========================================
// Invisible Overlay
@@ -147,8 +148,8 @@
// This edit-mode shortcut is fixed and is not currently configurable.
// ==========================================
"invisibleOverlay": {
"startupVisibility": "platform-default",
},
"startupVisibility": "platform-default" // Startup visibility setting.
}, // Startup behavior for the invisible interactive subtitle mining layer.
// ==========================================
// Keybindings (MPV Commands)
@@ -156,7 +157,7 @@
// Set command to null to disable a default keybinding.
// Hot-reload: keybinding changes apply live and update the session help modal on reopen.
// ==========================================
"keybindings": [],
"keybindings": [], // Extra keybindings that are merged with built-in defaults.
// ==========================================
// Subtitle Appearance
@@ -164,39 +165,45 @@
// Hot-reload: subtitle style changes apply live without restarting SubMiner.
// ==========================================
"subtitleStyle": {
"enableJlpt": false,
"fontFamily": "Noto Sans CJK JP Regular, Noto Sans CJK JP, Arial Unicode MS, Arial, sans-serif",
"fontSize": 35,
"fontColor": "#cad3f5",
"fontWeight": "normal",
"fontStyle": "normal",
"backgroundColor": "rgba(54, 58, 79, 0.5)",
"nPlusOneColor": "#c6a0f6",
"knownWordColor": "#a6da95",
"enableJlpt": false, // Enable JLPT vocabulary level underlines. When disabled, JLPT tagging lookup and underlines are skipped. Values: true | false
"fontFamily": "Noto Sans CJK JP Regular, Noto Sans CJK JP, Arial Unicode MS, Arial, sans-serif", // Font family setting.
"fontSize": 35, // Font size setting.
"fontColor": "#cad3f5", // Font color setting.
"fontWeight": "normal", // Font weight setting.
"fontStyle": "normal", // Font style setting.
"backgroundColor": "rgba(54, 58, 79, 0.5)", // Background color setting.
"nPlusOneColor": "#c6a0f6", // N plus one color setting.
"knownWordColor": "#a6da95", // Known word color setting.
"jlptColors": {
"N1": "#ed8796",
"N2": "#f5a97f",
"N3": "#f9e2af",
"N4": "#a6e3a1",
"N5": "#8aadf4",
},
"N1": "#ed8796", // N1 setting.
"N2": "#f5a97f", // N2 setting.
"N3": "#f9e2af", // N3 setting.
"N4": "#a6e3a1", // N4 setting.
"N5": "#8aadf4" // N5 setting.
}, // Jlpt colors setting.
"frequencyDictionary": {
"enabled": false,
"sourcePath": "",
"topX": 1000,
"mode": "single",
"singleColor": "#f5a97f",
"bandedColors": ["#ed8796", "#f5a97f", "#f9e2af", "#a6e3a1", "#8aadf4"],
},
"enabled": false, // Enable frequency-dictionary-based highlighting based on token rank. Values: true | false
"sourcePath": "", // Optional absolute path to a frequency dictionary directory. If empty, built-in discovery search paths are used.
"topX": 1000, // Only color tokens with frequency rank <= topX (default: 1000).
"mode": "single", // single: use one color for all matching tokens. banded: use color ramp by frequency band. Values: single | banded
"singleColor": "#f5a97f", // Color used when frequencyDictionary.mode is `single`.
"bandedColors": [
"#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.
"secondary": {
"fontSize": 24,
"fontColor": "#ffffff",
"backgroundColor": "transparent",
"fontWeight": "normal",
"fontStyle": "normal",
"fontFamily": "Noto Sans CJK JP Regular, Noto Sans CJK JP, Arial Unicode MS, Arial, sans-serif",
},
},
"fontSize": 24, // Font size setting.
"fontColor": "#ffffff", // Font color setting.
"backgroundColor": "transparent", // Background color setting.
"fontWeight": "normal", // Font weight setting.
"fontStyle": "normal", // Font style setting.
"fontFamily": "Noto Sans CJK JP Regular, Noto Sans CJK JP, Arial Unicode MS, Arial, sans-serif" // Font family setting.
} // Secondary setting.
}, // Primary and secondary subtitle styling.
// ==========================================
// Secondary Subtitles
@@ -205,59 +212,62 @@
// Hot-reload: defaultMode updates live while SubMiner is running.
// ==========================================
"secondarySub": {
"secondarySubLanguages": [],
"autoLoadSecondarySub": false,
"defaultMode": "hover",
},
"secondarySubLanguages": [], // Secondary sub languages setting.
"autoLoadSecondarySub": false, // Auto load secondary sub setting. Values: true | false
"defaultMode": "hover" // Default mode setting.
}, // Dual subtitle track options.
// ==========================================
// Auto Subtitle Sync
// Subsync engine and executable paths.
// ==========================================
"subsync": {
"defaultMode": "auto",
"alass_path": "",
"ffsubsync_path": "",
"ffmpeg_path": "",
},
"defaultMode": "auto", // Subsync default mode. Values: auto | manual
"alass_path": "", // Alass path setting.
"ffsubsync_path": "", // Ffsubsync path setting.
"ffmpeg_path": "" // Ffmpeg path setting.
}, // Subsync engine and executable paths.
// ==========================================
// Subtitle Position
// Initial vertical subtitle position from the bottom.
// ==========================================
"subtitlePosition": {
"yPercent": 10,
},
"yPercent": 10 // Y percent setting.
}, // Initial vertical subtitle position from the bottom.
// ==========================================
// Jimaku
// Jimaku API configuration and defaults.
// ==========================================
"jimaku": {
"apiBaseUrl": "https://jimaku.cc",
"languagePreference": "ja",
"maxEntryResults": 10,
},
"apiBaseUrl": "https://jimaku.cc", // Api base url setting.
"languagePreference": "ja", // Preferred language used in Jimaku search. Values: ja | en | none
"maxEntryResults": 10 // Maximum Jimaku search results returned.
}, // Jimaku API configuration and defaults.
// ==========================================
// YouTube Subtitle Generation
// Defaults for subminer YouTube subtitle extraction/transcription mode.
// ==========================================
"youtubeSubgen": {
"mode": "automatic",
"whisperBin": "",
"whisperModel": "",
"primarySubLanguages": ["ja", "jpn"],
},
"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.
"whisperModel": "", // Path to whisper model used for fallback transcription.
"primarySubLanguages": [
"ja",
"jpn"
] // Comma-separated primary subtitle language priority used by the launcher.
}, // Defaults for subminer YouTube subtitle extraction/transcription mode.
// ==========================================
// Anilist
// Anilist API credentials and update behavior.
// ==========================================
"anilist": {
"enabled": false,
"accessToken": "",
},
"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.
}, // Anilist API credentials and update behavior.
// ==========================================
// Jellyfin
@@ -265,25 +275,33 @@
// Access token is stored in config and should be treated as a secret.
// ==========================================
"jellyfin": {
"enabled": false,
"serverUrl": "",
"username": "",
"accessToken": "",
"userId": "",
"deviceId": "subminer",
"clientName": "SubMiner",
"clientVersion": "0.1.0",
"defaultLibraryId": "",
"remoteControlEnabled": true,
"remoteControlAutoConnect": true,
"autoAnnounce": false,
"remoteControlDeviceName": "SubMiner",
"pullPictures": false,
"iconCacheDir": "/tmp/subminer-jellyfin-icons",
"directPlayPreferred": true,
"directPlayContainers": ["mkv", "mp4", "webm", "mov", "flac", "mp3", "aac"],
"transcodeVideoCodec": "h264",
},
"enabled": false, // Enable optional Jellyfin integration and CLI control commands. Values: true | false
"serverUrl": "", // Base Jellyfin server URL (for example: http://localhost:8096).
"username": "", // Default Jellyfin username used during CLI login.
"accessToken": "", // Access token setting.
"userId": "", // User id setting.
"deviceId": "subminer", // Device id setting.
"clientName": "SubMiner", // Client name setting.
"clientVersion": "0.1.0", // Client version setting.
"defaultLibraryId": "", // Optional default Jellyfin library ID for item listing.
"remoteControlEnabled": true, // Enable Jellyfin remote cast control mode. Values: true | false
"remoteControlAutoConnect": true, // Auto-connect to the configured remote control target. Values: true | false
"autoAnnounce": false, // When enabled, automatically trigger remote announce/visibility check on websocket connect. Values: true | false
"remoteControlDeviceName": "SubMiner", // Device name reported for Jellyfin remote control sessions.
"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.
"directPlayPreferred": true, // Try direct play before server-managed transcoding when possible. Values: true | false
"directPlayContainers": [
"mkv",
"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.
// ==========================================
// Immersion Tracking
@@ -292,19 +310,19 @@
// Policy tuning is available for queue, flush, and retention values.
// ==========================================
"immersionTracking": {
"enabled": true,
"dbPath": "",
"batchSize": 25,
"flushIntervalMs": 500,
"queueCap": 1000,
"payloadCapBytes": 256,
"maintenanceIntervalMs": 86400000,
"enabled": true, // Enable immersion tracking for mined subtitle metadata. Values: true | false
"dbPath": "", // Optional SQLite database path for immersion tracking. Empty value uses the default app data path.
"batchSize": 25, // Buffered telemetry/event writes per SQLite transaction.
"flushIntervalMs": 500, // Max delay before queue flush in milliseconds.
"queueCap": 1000, // In-memory write queue cap before overflow policy applies.
"payloadCapBytes": 256, // Max JSON payload size per event before truncation.
"maintenanceIntervalMs": 86400000, // Maintenance cadence (prune + rollup + vacuum checks).
"retention": {
"eventsDays": 7,
"telemetryDays": 30,
"dailyRollupsDays": 365,
"monthlyRollupsDays": 1825,
"vacuumIntervalDays": 7,
},
},
"eventsDays": 7, // Raw event retention window in days.
"telemetryDays": 30, // Telemetry retention window in days.
"dailyRollupsDays": 365, // Daily rollup retention window in days.
"monthlyRollupsDays": 1825, // Monthly rollup retention window in days.
"vacuumIntervalDays": 7 // Minimum days between VACUUM runs.
} // Retention setting.
} // Enable/disable immersion tracking.
}

132
docs/shortcuts.md Normal file
View File

@@ -0,0 +1,132 @@
# Keyboard Shortcuts
All shortcuts are configurable in `config.jsonc` under `shortcuts` and `keybindings`. Set any shortcut to `null` to disable it.
## Global Shortcuts
These work system-wide regardless of which window has focus.
| Shortcut | Action | Configurable |
| -------- | ------ | ------------ |
| `Alt+Shift+O` | Toggle visible overlay | `shortcuts.toggleVisibleOverlayGlobal` |
| `Alt+Shift+I` | Toggle invisible overlay | `shortcuts.toggleInvisibleOverlayGlobal` |
| `Alt+Shift+Y` | Open Yomitan settings | Fixed (not configurable) |
::: tip
Global shortcuts are registered with the OS. If they conflict with another application, update them in `shortcuts` config and restart SubMiner.
:::
## Mining Shortcuts
These work when the overlay window has focus.
| Shortcut | Action | Config key |
| -------- | ------ | ---------- |
| `Ctrl/Cmd+S` | Mine current subtitle as sentence card | `shortcuts.mineSentence` |
| `Ctrl/Cmd+Shift+S` | Mine multiple lines (press 19 to select count) | `shortcuts.mineSentenceMultiple` |
| `Ctrl/Cmd+C` | Copy current subtitle text | `shortcuts.copySubtitle` |
| `Ctrl/Cmd+Shift+C` | Copy multiple lines (press 19 to select count) | `shortcuts.copySubtitleMultiple` |
| `Ctrl/Cmd+V` | Update last Anki card from clipboard text | `shortcuts.updateLastCardFromClipboard` |
| `Ctrl/Cmd+G` | Trigger field grouping (Kiku merge check) | `shortcuts.triggerFieldGrouping` |
| `Ctrl/Cmd+Shift+A` | Mark last card as audio card | `shortcuts.markAudioCard` |
The multi-line shortcuts open a digit selector with a 3-second timeout (`shortcuts.multiCopyTimeoutMs`). Press `1``9` to select how many recent subtitle lines to combine.
## Overlay Controls
These control playback and subtitle display. They require overlay window focus.
| Shortcut | Action |
| -------- | ------ |
| `Space` | Toggle mpv pause |
| `ArrowRight` | Seek forward 5 seconds |
| `ArrowLeft` | Seek backward 5 seconds |
| `ArrowUp` | Seek forward 60 seconds |
| `ArrowDown` | Seek backward 60 seconds |
| `Shift+H` | Jump to previous subtitle |
| `Shift+L` | Jump to next subtitle |
| `Ctrl+Shift+H` | Replay current subtitle (play to end, then pause) |
| `Ctrl+Shift+L` | Play next subtitle (jump, play to end, then pause) |
| `Q` | Quit mpv |
| `Ctrl+W` | Quit mpv |
| `Right-click` | Toggle pause (outside subtitle area) |
| `Right-click + drag` | Reposition subtitles (on subtitle area) |
| `Ctrl/Cmd+A` | Append clipboard video path to mpv playlist |
These keybindings can be overridden or disabled via the `keybindings` config array.
## Subtitle & Feature Shortcuts
| Shortcut | Action | Config key |
| -------- | ------ | ---------- |
| `Ctrl/Cmd+Shift+V` | Cycle secondary subtitle mode (hidden → visible → hover) | `shortcuts.toggleSecondarySub` |
| `Ctrl/Cmd+Shift+O` | Open runtime options palette | `shortcuts.openRuntimeOptions` |
| `Ctrl+Shift+J` | Open Jimaku subtitle search modal | `shortcuts.openJimaku` |
| `Ctrl+Alt+S` | Open subtitle sync (subsync) modal | `shortcuts.triggerSubsync` |
## Invisible Subtitle Position Edit Mode
Enter edit mode to fine-tune invisible overlay alignment with mpv's native subtitles.
| Shortcut | Action |
| -------- | ------ |
| `Ctrl/Cmd+Shift+P` | Toggle position edit mode |
| `ArrowKeys` or `hjkl` | Nudge position by 1 px |
| `Shift+Arrow` | Nudge position by 4 px |
| `Enter` or `Ctrl+S` | Save position and exit edit mode |
| `Esc` | Cancel and discard changes |
## MPV Plugin Chords
When the mpv plugin is installed, all commands use a `y` chord prefix — press `y`, then the second key within 1 second.
| Chord | Action |
| ----- | ------ |
| `y-y` | Open SubMiner menu (OSD) |
| `y-s` | Start overlay |
| `y-S` | Stop overlay |
| `y-t` | Toggle visible overlay |
| `y-i` | Toggle invisible overlay |
| `y-I` | Show invisible overlay |
| `y-u` | Hide invisible overlay |
| `y-o` | Open Yomitan settings |
| `y-r` | Restart overlay |
| `y-c` | Check overlay status |
| `y-d` | Toggle overlay DevTools (dev/debug use) |
## Drag-and-Drop
| Gesture | Action |
| ------- | ------ |
| Drop file(s) onto overlay | Replace current mpv playlist with dropped files |
| `Shift` + drop file(s) | Append all dropped files to current mpv playlist |
## Customizing Shortcuts
All `shortcuts.*` keys accept [Electron accelerator strings](https://www.electronjs.org/docs/latest/api/accelerator), for example `"CommandOrControl+Shift+M"`. Use `null` to disable a shortcut.
```jsonc
{
"shortcuts": {
"mineSentence": "CommandOrControl+S",
"copySubtitle": "CommandOrControl+C",
"toggleVisibleOverlayGlobal": "Alt+Shift+O",
"toggleInvisibleOverlayGlobal": "Alt+Shift+I",
"openJimaku": null // disabled
}
}
```
The `keybindings` array overrides or extends the overlay's built-in key handling for mpv commands:
```jsonc
{
"keybindings": [
{ "key": "f", "command": ["cycle", "fullscreen"] },
{ "key": "m", "command": ["cycle", "mute"] },
{ "key": "Space", "command": null } // disable default Space → pause
]
}
```
Both `shortcuts` and `keybindings` are [hot-reloadable](/configuration#hot-reload-behavior) — changes take effect without restarting SubMiner.

View File

@@ -4,4 +4,7 @@ Read first. Keep concise.
| agent_id | alias | mission | status | file | last_update_utc |
| ------------ | -------------- | ---------------------------------------------------- | --------- | ------------------------------------- | ---------------------- |
| `codex-main` | `planner-exec` | `Unify config path resolution across app + launcher` | `handoff` | `docs/subagents/agents/codex-main.md` | `2026-02-19T09:05:26Z` |
| `codex-main` | `planner-exec` | `Fix frequency/N+1 regression in plugin --start flow` | `in_progress` | `docs/subagents/agents/codex-main.md` | `2026-02-19T19:36:46Z` |
| `codex-config-validation-20260219T172015Z-iiyf` | `codex-config-validation` | `Find root cause of config validation error for ~/.config/SubMiner/config.jsonc` | `completed` | `docs/subagents/agents/codex-config-validation-20260219T172015Z-iiyf.md` | `2026-02-19T17:26:17Z` |
| `codex-task85-20260219T233711Z-46hc` | `codex-task85` | `Resume TASK-85 maintainability refactor from latest handoff point` | `in_progress` | `docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md` | `2026-02-20T00:00:55Z` |
| `codex-anilist-deeplink-20260219T233926Z` | `anilist-deeplink` | `Fix external subminer:// AniList callback handling from browser` | `done` | `docs/subagents/agents/codex-anilist-deeplink-20260219T233926Z.md` | `2026-02-19T23:59:21Z` |

View File

@@ -1,11 +1,11 @@
# Agent: <agent_id>
# Agent: `<agent_id>`
- alias: <short label>
- mission: <one-line focus>
- status: <planning|editing|testing|blocked|handoff|done>
- branch: <name>
- started_at: <UTC ISO>
- heartbeat_minutes: <n>
- alias: `<short label>`
- mission: `<one-line focus>`
- status: `<planning|editing|testing|blocked|handoff|done>`
- branch: `<name>`
- started_at: `<UTC ISO>`
- heartbeat_minutes: `<n>`
## Current Work (newest first)
- [YYYY-MM-DDTHH:MM:SSZ] intent: ...

View File

@@ -0,0 +1,38 @@
# Agent: codex-anilist-deeplink-20260219T233926Z
- alias: anilist-deeplink
- mission: Fix external subminer:// AniList callback handling from browser
- status: done
- branch: main
- started_at: 2026-02-19T23:39:26Z
- heartbeat_minutes: 5
## Current Work (newest first)
- [2026-02-19T23:59:21Z] handoff: updated backlog/docs/config artifacts for AniList flow completion; TASK-29.3 marked Done with AC/DoD checked and implementation notes.
- [2026-02-19T23:59:21Z] test: `bun run generate:config-example` pass (includes build), `bun test src/main/runtime/anilist-setup.test.ts` pass (11/11).
- [2026-02-19T23:42:42Z] progress: added app-level protocol registration (`setAsDefaultProtocolClient`) plus `open-url` and `second-instance` deep-link listeners; AniList token consumption moved to shared handler so browser callback `subminer://anilist-setup?access_token=...` saves token even when setup window is not navigating.
- [2026-02-19T23:42:42Z] test: `bun test src/main/runtime/anilist-setup.test.ts` pass (11/11); `bun run build` pass.
- [2026-02-19T23:39:26Z] intent: fix AniList callback deep-link handoff from external browser to app protocol handler; verify token is consumed and setup resolves without requiring in-window nav interception.
- [2026-02-19T23:39:26Z] planned files: `src/main.ts`, `src/main/runtime/anilist-setup.ts`, `src/main/runtime/anilist-setup.test.ts`, `backlog/tasks/task-29.3 - Fix-AniList-OAuth-callback-token-handling-in-setup-window.md`.
- [2026-02-19T23:39:26Z] assumptions: worker emits valid `subminer://anilist-setup?access_token=...` URL; failure is missing app-level protocol registration/open-url handling.
## Files Touched
- `docs/subagents/INDEX.md`
- `docs/subagents/agents/codex-anilist-deeplink-20260219T233926Z.md`
- `src/main.ts`
- `src/main/runtime/anilist-setup.ts`
- `src/main/runtime/anilist-setup.test.ts`
- `backlog/tasks/task-29.3 - Fix-AniList-OAuth-callback-token-handling-in-setup-window.md`
- `docs/configuration.md`
- `src/config/definitions.ts`
- `config.example.jsonc`
- `docs/public/config.example.jsonc`
## Assumptions
- Existing TASK-29.3 covers this bugfix; no new backlog ticket needed.
## Open Questions / Blockers
- none
## Next Step
- Wait for next user direction.

View File

@@ -0,0 +1,39 @@
# Agent Log
- agent_id: `codex-config-validation-20260219T172015Z-iiyf`
- alias: `codex-config-validation`
- mission: `Find root cause of config validation error for ~/.config/SubMiner/config.jsonc`
- status: `completed`
- last_update_utc: `2026-02-19T17:26:17Z`
## Intent
- inspect validation code path + expected schema
- inspect user config values
- map failing field/type; report exact issue
## Planned Files
- `~/.config/SubMiner/config.jsonc`
- `src/**` (validation/schema)
## Assumptions
- config error produced by current repo binary/schema
- user wants diagnosis only; no edits unless asked
## Run Notes
- phase: `inspect -> reproduce -> handoff`
- strict parse: ok (`reloadConfigStrict` succeeded)
- warnings:
- `ankiConnect.openRouter` deprecated; use `ankiConnect.ai`
- `ankiConnect.isLapis.sentenceCardSentenceField` deprecated; fixed internal value
- `ankiConnect.isLapis.sentenceCardAudioField` deprecated; fixed internal value
## Handoff
- touched files: `docs/subagents/INDEX.md`, `docs/subagents/agents/codex-config-validation-20260219T172015Z-iiyf.md`, `src/main/config-validation.ts`, `src/main/runtime/startup-config.ts`, `src/core/services/config-hot-reload.ts`, `src/main.ts`, `src/main/config-validation.test.ts`, `src/main/runtime/startup-config.test.ts`, `src/core/services/config-hot-reload.test.ts`
- key decision: report warnings as likely user-visible "config validation issue(s)" root cause; then implement detailed warning bodies for startup + hot-reload notifications
- blocker: none
- next step: optional tune max-lines/format for notification bodies

View File

@@ -1,14 +1,40 @@
# Agent: codex-main
- alias: planner-exec
- mission: Unify config path resolution across app + launcher
- status: handoff
- mission: Fix frequency/N+1 regression in plugin --start flow
- status: in_progress
- branch: main
- started_at: 2026-02-19T08:06:28Z
- heartbeat_minutes: 20
## Current Work (newest first)
- [2026-02-19T19:36:46Z] progress: config confirmed frequency enabled (`~/.config/SubMiner/config.jsonc`); likely mode-latch issue after initial `--texthooker`.
- [2026-02-19T19:36:46Z] change: in `src/main.ts`, `handleCliCommand` now disables `texthookerOnlyMode` on follow-up `--start`/overlay commands and triggers background warmups.
- [2026-02-19T19:36:46Z] test: `bun run build` pass; `bun test src/core/services/cli-command.test.ts src/cli/args.test.ts src/core/services/startup-bootstrap.test.ts` pass (28/28).
- [2026-02-19T19:29:00Z] progress: found second-instance toggle gap: MPV connect was gated to initial-source toggles only; plugin toggle handoff could show overlay without subtitle stream/tokenization.
- [2026-02-19T19:29:00Z] change: updated `src/core/services/cli-command.ts` so toggle/toggle-visible/toggle-invisible trigger MPV connect regardless of source when app is already running.
- [2026-02-19T19:29:00Z] test: added `handleCliCommand connects MPV for toggle on second-instance`; `bun test src/core/services/cli-command.test.ts` pass (19/19).
- [2026-02-19T19:24:18Z] progress: root cause confirmed: subtitle tokenization is skipped when no overlay windows (`src/main.ts`), and `--start` command path did not initialize overlay runtime.
- [2026-02-19T19:24:18Z] change: `src/core/services/cli-command.ts` now initializes overlay runtime for `--start`; second-instance `--start` is ignored only when overlay runtime is already initialized.
- [2026-02-19T19:24:18Z] test: `src/core/services/cli-command.test.ts` covers start-driven init + second-instance behaviors; `bun test src/core/services/cli-command.test.ts` pass (18/18).
- [2026-02-19T19:04:58Z] intent: investigate report that frequency tracking resolves from app config location instead of active app binary root during just-built binary tests; add regression test first, then patch runtime path resolution.
- [2026-02-19T19:04:58Z] planned files: `src/main/frequency-dictionary-runtime.ts`, `src/core/services/frequency-dictionary.ts`, `src/core/services/frequency-dictionary.test.ts`, `backlog/tasks/*` (ticket link).
- [2026-02-19T19:04:58Z] assumptions: runtime should prefer binary-adjacent frequency assets when `SUBMINER_BINARY_PATH` points to a built app binary; config-path-based defaults should remain fallback.
- [2026-02-19T19:05:00Z] backlog: linked work to `TASK-87` (`backlog/tasks/task-87 - Resolve-frequency-storage-path-relative-to-active-app-binary.md`).
- [2026-02-19T16:58:00Z] intent: investigate AniList login regression (`unsupported_grant_type`), add callback token-consumption tests first, then wire `openAnilistSetupWindow` navigation handlers to persist OAuth token from callback URL/hash.
- [2026-02-19T16:54:18Z] handoff: implemented inline same-line option comments in config template output, including enum/boolean value lists where known; regenerated `config.example.jsonc` and `docs/public/config.example.jsonc`.
- [2026-02-19T16:54:18Z] test: `bun run build && node --test dist/config/config.test.js` -> pass (33/33); macOS helper compile sandbox-denied cache path fallback unchanged.
- [2026-02-19T16:51:29Z] intent: user requested inline same-line comments for each example config option with short purpose + allowed values where enum-like; target `config.example.jsonc` (+ `docs/public/config.example.jsonc` parity if needed).
- [2026-02-19T10:24:42Z] progress: continued TASK-85 decomposition slices in `src/main.ts`; extracted config-derived runtime wrappers, AniList setup helpers, clipboard queue runtime, AniList state runtime, immersion media runtime, startup config runtime, and main subsync runtime under `src/main/runtime/`; updated call sites.
- [2026-02-19T10:24:42Z] test: `bun run build` pass (sandbox Swift cache fallback unchanged), `node --test dist/main/runtime/anilist-setup.test.js dist/main/runtime/clipboard-queue.test.js dist/main/runtime/anilist-state.test.js dist/main/runtime/immersion-media.test.js dist/main/runtime/immersion-startup.test.js dist/main/runtime/startup-config.test.js dist/main/config-validation.test.js dist/core/services/app-ready.test.js dist/core/services/startup-bootstrap.test.js` -> pass (35/35).
- [2026-02-19T10:05:25Z] progress: continued TASK-85 refactor execution; extracted app-ready config runtime handlers to `src/main/runtime/startup-config.ts` + tests and immersion tracker startup handler to `src/main/runtime/immersion-startup.ts` + tests; wired `src/main.ts` call sites to factories; `src/main.ts` now 3250 LOC.
- [2026-02-19T10:05:25Z] test: `bun run build` pass (same macOS helper sandbox fallback), `node --test dist/main/runtime/immersion-startup.test.js dist/main/runtime/startup-config.test.js dist/main/config-validation.test.js dist/core/services/app-ready.test.js dist/core/services/startup-bootstrap.test.js` -> pass (24/24).
- [2026-02-19T09:55:47Z] handoff: initialized Backlog non-interactive (`backlog init --defaults --integration-mode mcp --auto-open-browser false`), moved TASK-85 to In Progress, completed Task 2 guardrail slice (`scripts/check-file-budgets.ts`, docs, package scripts), and started Task 3 with first `src/main.ts` extraction (`src/main/config-validation.ts` + tests); build/tests pass.
- [2026-02-19T09:55:47Z] test: `bun run check:file-budgets` (warn list), `bun run check:file-budgets:strict` (expected fail), `bun run build` (pass; macOS helper falls back to source due sandbox cache permission), `node --test dist/main/config-validation.test.js dist/core/services/app-ready.test.js dist/core/services/startup-bootstrap.test.js` (15/15 pass).
- [2026-02-19T09:47:52Z] handoff: delivered concrete maintainability plan at `docs/plans/2026-02-19-repo-maintainability-refactor-plan.md` plus umbrella ticket `TASK-85`; ready for execution-mode choice.
- [2026-02-19T09:45:26Z] intent: user requested concrete maintainability/readability plan via writing-plans + refactor skills; focus hotspots `src/main.ts`, `src/anki-integration.ts`, `plugin/subminer.lua`, generated `subminer` launcher artifact strategy.
- [2026-02-19T09:45:26Z] progress: scanned LOC hotspots and module graph; confirmed `subminer` is generated/ignored build artifact (`make build-launcher`) and major live hotspots are `src/main.ts` (3316 LOC), `src/anki-integration.ts` (1703 LOC), `src/config/service.ts` (1565 LOC), `src/core/services/immersion-tracker-service.ts` (1470 LOC).
- [2026-02-19T09:05:26Z] handoff: completed TASK-70 (Done); unified config path resolution into shared `src/config/path-resolution.ts`, wired app+launcher call sites, added precedence tests, verified build/tests/launcher bundle, and checked AC/DoD in Backlog.
- [2026-02-19T09:05:26Z] test: `bun run build && node --test dist/config/path-resolution.test.js dist/config/config.test.js && bun build ./launcher/main.ts --target=bun --packages=bundle --outfile=/tmp/subminer-task70` -> pass.
- [2026-02-19T08:57:36Z] progress: plan saved to `docs/plans/2026-02-19-task-70-unify-config-path-resolution.md`, mirrored to backlog TASK-70 `planSet`; moving to edit/test execution.
@@ -62,6 +88,13 @@
- `docs/mining-workflow.md`
- `backlog/tasks/task-83 - Simplify-isLapis-sentence-card-field-config-to-fixed-field-names.md`
- `backlog/tasks/task-38 - Add-user-friendly-config-validation-errors-on-startup.md`
- `docs/plans/2026-02-19-repo-maintainability-refactor-plan.md`
- `backlog/tasks/task-85 - Refactor-large-files-for-maintainability-and-readability.md`
- `backlog/config.yml`
- `scripts/check-file-budgets.ts`
- `docs/file-size-budgets.md`
- `src/main/config-validation.ts`
- `src/main/config-validation.test.ts`
## Assumptions
@@ -73,4 +106,17 @@
## Next Step
- Await user review; optional next step is commit for TASK-70 changes.
- Continue Task 3: extract next `src/main.ts` runtime slice (startup reload/logging branch) into `src/main/runtime/*` with seam tests, then re-run build + targeted core tests.
- [2026-02-19T17:20:00Z] intent: improve launcher help output so top-level help auto-lists available subcommands from command registry; add/adjust tests if present.
- [2026-02-19T16:56:43Z] intent: improve launcher help output so top-level help auto-lists available subcommands from command registry; add/adjust tests if present.
- [2026-02-19T16:59:23Z] progress: added auto-generated root help subcommand section by rendering Commander subcommand registry (`launcher/config.ts`) and covered with launcher help regression test (`launcher/config.test.ts`).
- [2026-02-19T16:59:23Z] test: `bun test launcher/config.test.ts` pass; `make build-launcher && ./subminer -h` shows Commands list with jellyfin/yt/doctor/config/mpv/texthooker.
- [2026-02-19T16:59:23Z] handoff: completed requested launcher help improvement; top-level `-h` now includes available subcommands without hard-coded help string updates.
- [2026-02-19T17:07:25Z] progress: added launcher passthrough subcommand `app|bin` that forwards raw args directly to SubMiner binary (`launcher/config.ts`, `launcher/main.ts`, `launcher/types.ts`).
- [2026-02-19T17:07:25Z] test: `bun test launcher/config.test.ts launcher/parse-args.test.ts` pass; passthrough smoke via stub binary env (`SUBMINER_APPIMAGE_PATH=/tmp/subminer-app-stub.sh bun run launcher/main.ts app --anilist --anilist-status`) forwards raw args; `make build-launcher && ./subminer -h` shows `app|bin`.
- [2026-02-19T17:07:25Z] handoff: user-requested direct app/binary passthrough implemented; supports AniList trigger via `subminer app --anilist`.
- [2026-02-19T17:34:30Z] progress: hardened `app|bin` passthrough to bypass Commander parsing and forward raw argv suffix verbatim after subcommand token (`launcher/config.ts`).
- [2026-02-19T17:34:30Z] test: `bun test launcher/config.test.ts launcher/parse-args.test.ts` pass; stub verification confirms exact forwarding for `app --start --anilist-setup -h`; rebuilt wrapper via `make build-launcher`.

View File

@@ -0,0 +1,49 @@
# Agent: codex-task85-20260219T233711Z-46hc
- alias: codex-task85
- mission: Resume TASK-85 maintainability refactor from latest handoff point
- status: in_progress
- branch: main
- started_at: 2026-02-19T23:37:11Z
- heartbeat_minutes: 5
## Current Work (newest first)
- [2026-02-20T00:00:55Z] progress: extracted AniList setup/protocol handlers (`notifyAnilistSetup`, `consumeAnilistSetupTokenFromUrl`, `handleAnilistSetupProtocolUrl`, `registerSubminerProtocolClient`) from `src/main.ts` into `src/main/runtime/anilist-setup-protocol.ts`; rewired protocol registration and callback wiring; `src/main.ts` now 2988 LOC.
- [2026-02-20T00:00:55Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/anilist-setup-protocol.test.js dist/main/runtime/jellyfin-remote-connection.test.js dist/main/runtime/jellyfin-remote-playback.test.js dist/main/runtime/jellyfin-remote-commands.test.js dist/main/runtime/config-hot-reload-handlers.test.js dist/core/services/app-ready.test.js dist/core/services/startup-bootstrap.test.js` pass (35/35).
- [2026-02-19T23:58:32Z] progress: extracted Jellyfin MPV connection/bootstrap handlers (`waitForMpvConnected`, `launchMpvIdleForJellyfinPlayback`, `ensureMpvConnectedForJellyfinPlayback`) from `src/main.ts` into `src/main/runtime/jellyfin-remote-connection.ts`; rewired call sites; `src/main.ts` now 2996 LOC.
- [2026-02-19T23:58:32Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/jellyfin-remote-connection.test.js dist/main/runtime/jellyfin-remote-playback.test.js dist/main/runtime/jellyfin-remote-commands.test.js dist/main/runtime/config-hot-reload-handlers.test.js dist/core/services/app-ready.test.js dist/core/services/startup-bootstrap.test.js` pass (31/31).
- [2026-02-19T23:50:09Z] progress: extracted Jellyfin remote playback reporting logic (`secondsToJellyfinTicks`, `reportJellyfinRemoteProgress`, `reportJellyfinRemoteStopped`) from `src/main.ts` into `src/main/runtime/jellyfin-remote-playback.ts`; rewired main runtime handlers.
- [2026-02-19T23:50:09Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/jellyfin-remote-playback.test.js dist/main/runtime/jellyfin-remote-commands.test.js dist/main/runtime/config-hot-reload-handlers.test.js dist/core/services/app-ready.test.js dist/core/services/startup-bootstrap.test.js` pass (28/28).
- [2026-02-19T23:48:01Z] progress: extracted Jellyfin remote command/session parsing handlers from `src/main.ts` into `src/main/runtime/jellyfin-remote-commands.ts` (`getConfiguredJellyfinSession`, `handleJellyfinRemotePlay`, `handleJellyfinRemotePlaystate`, `handleJellyfinRemoteGeneralCommand` factories); rewired `src/main.ts` to use handler factories.
- [2026-02-19T23:48:01Z] test: `bun run build` pass (expected macOS helper Swift cache fallback) + `node --test dist/main/runtime/jellyfin-remote-commands.test.js dist/main/runtime/config-hot-reload-handlers.test.js dist/core/services/app-ready.test.js dist/core/services/startup-bootstrap.test.js` pass (24/24).
- [2026-02-19T23:40:59Z] progress: extracted config hot-reload apply/message callbacks from `src/main.ts` into `src/main/runtime/config-hot-reload-handlers.ts`; `src/main.ts` now 3096 LOC (down from 3116 at session start).
- [2026-02-19T23:40:59Z] test: `bun run build` pass (expected macOS helper Swift cache fallback in sandbox) + `node --test dist/main/runtime/config-hot-reload-handlers.test.js dist/core/services/app-ready.test.js dist/core/services/startup-bootstrap.test.js` pass (19/19).
- [2026-02-19T23:37:11Z] intent: review TASK-85 state + subagent handoff, then continue next refactor slice from prior checkpoint.
- [2026-02-19T23:37:11Z] planned files: `src/main.ts`, `src/main/runtime/*`, `src/main/runtime/*.test.ts`, `docs/subagents/INDEX.md`, `docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md`.
- [2026-02-19T23:37:11Z] assumptions: backlog MCP not initialized in this workspace; continue using existing `backlog/tasks/task-85 - Refactor-large-files-for-maintainability-and-readability.md` as source of truth.
## Files Touched
- `docs/subagents/INDEX.md`
- `docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md`
- `src/main.ts`
- `src/main/runtime/config-hot-reload-handlers.ts`
- `src/main/runtime/config-hot-reload-handlers.test.ts`
- `src/main/runtime/jellyfin-remote-commands.ts`
- `src/main/runtime/jellyfin-remote-commands.test.ts`
- `src/main/runtime/jellyfin-remote-playback.ts`
- `src/main/runtime/jellyfin-remote-playback.test.ts`
- `src/main/runtime/jellyfin-remote-connection.ts`
- `src/main/runtime/jellyfin-remote-connection.test.ts`
- `src/main/runtime/anilist-setup-protocol.ts`
- `src/main/runtime/anilist-setup-protocol.test.ts`
## Open Questions / Blockers
- none
## Next Step
- identify next extractable `src/main.ts` domain slice, add seam test, extract to `src/main/runtime/*`, run build + targeted tests.
- extract next `src/main.ts` slice likely Jellyfin setup UI branch (`openJellyfinSetupWindow` form handling + config patch helpers) into `src/main/runtime/jellyfin-setup-window.ts` with seam tests.

View File

@@ -4,3 +4,4 @@ Shared notes. Append-only.
- [YYYY-MM-DDTHH:MM:SSZ] [agent_id|alias] note, question, dependency, conflict, decision.
- [2026-02-19T08:21:11Z] [codex-main|planner-exec] conflict note: `docs/subagents/INDEX.md` and `docs/subagents/agents/codex-main.md` were externally updated to TASK-69 while TASK-38 work was in-flight; reconciled own row/file back to TASK-38 handoff state.
- [2026-02-20T00:01:40Z] [codex-anilist-deeplink|anilist-deeplink] preparing commit; scoping staged set to repo changes, excluding external reference dirs (vendor/yomitan-jlpt-vocab, mpv-anilist-updater).

View File

@@ -68,7 +68,7 @@ Shown when SubMiner tries to update a card that no longer exists, or when AnkiCo
- Renderer errors now trigger an automatic recovery path. You should see a short toast ("Renderer error recovered. Overlay is still running.").
- Recovery closes any open modal and restores click-through/shortcuts automatically without interrupting mpv playback.
- If errors keep recurring, open DevTools (`Alt+Shift+I`) and inspect the `renderer overlay recovery` error payload for stack trace + modal/subtitle context.
- If errors keep recurring, toggle the overlay's DevTools using the `y-d` mpv chord (or `F12` when running with `--dev`) and inspect the `renderer overlay recovery` error payload for stack trace + modal/subtitle context.
**Overlay is on the wrong monitor or position**

View File

@@ -58,6 +58,7 @@ subminer mpv socket # Print active mpv socket path
subminer mpv status # Exit 0 if socket is ready, else exit 1
subminer mpv idle # Launch detached idle mpv with SubMiner defaults
subminer texthooker # Launch texthooker-only mode
subminer app --anilist # Pass args directly to SubMiner binary (example: AniList login flow)
subminer yt -o ~/subs https://youtu.be/... # YouTube subcommand: output directory shortcut
subminer yt --mode preprocess --whisper-bin /path/to/whisper-cli --whisper-model /path/to/model.bin https://youtu.be/... # Pre-generate subtitle tracks before playback
@@ -103,6 +104,7 @@ SubMiner.AppImage --help # Show all options
- `subminer config`: config helpers (`path`, `show`).
- `subminer mpv`: mpv helpers (`status`, `socket`, `idle`).
- `subminer texthooker`: texthooker-only shortcut (same behavior as `--texthooker`).
- `subminer app` / `subminer bin`: direct passthrough to the SubMiner binary/AppImage.
- Subcommand help pages are available (for example `subminer jellyfin -h`, `subminer yt -h`).
Use subcommands for Jellyfin/YouTube command families (`subminer jellyfin ...`, `subminer yt ...`).