mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
refactor(main): modularize runtime and harden anilist setup flow
This commit is contained in:
@@ -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' },
|
||||
],
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
21
docs/file-size-budgets.md
Normal 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.
|
||||
@@ -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>
|
||||
|
||||
@@ -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
132
docs/shortcuts.md
Normal 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 1–9 to select count) | `shortcuts.mineSentenceMultiple` |
|
||||
| `Ctrl/Cmd+C` | Copy current subtitle text | `shortcuts.copySubtitle` |
|
||||
| `Ctrl/Cmd+Shift+C` | Copy multiple lines (press 1–9 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.
|
||||
@@ -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` |
|
||||
|
||||
@@ -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: ...
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
@@ -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`.
|
||||
|
||||
49
docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md
Normal file
49
docs/subagents/agents/codex-task85-20260219T233711Z-46hc.md
Normal 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.
|
||||
@@ -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).
|
||||
|
||||
@@ -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**
|
||||
|
||||
|
||||
@@ -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 ...`).
|
||||
|
||||
Reference in New Issue
Block a user