mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
fix(plugin): allow cold-start overlay launch without running process
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
---
|
||||
id: TASK-116
|
||||
title: Analyze git history and draft initial release cleanup plan
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-23 04:40'
|
||||
updated_date: '2026-02-23 04:44'
|
||||
labels:
|
||||
- release
|
||||
- git
|
||||
- planning
|
||||
dependencies: []
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Review current main branch commit history and project structure, recommend best pre-release history cleanup strategy, and produce a copy/paste command plan in initial-release.md.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Current commit history and repo state are analyzed
|
||||
- [x] #2 Recommended history-cleanup strategy is justified
|
||||
- [x] #3 initial-release.md contains exact copy/paste commands with safety checks and team cutover steps
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Analyzed `main` history and repository state, then recommended an orphan-branch history rewrite with curated commits as the safest pre-release cleanup path. Added `initial-release.md` with exact copy/paste commands for backups, optional stash handling, orphan branch creation, 7-commit split staging strategy, tree-equivalence validation, force-with-lease cutover, tagging, and collaborator resync instructions.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
id: TASK-117
|
||||
title: Fix mpv plugin overlay start gate when SubMiner is not running
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-02-23 04:49'
|
||||
updated_date: '2026-02-23 04:50'
|
||||
labels:
|
||||
- bug
|
||||
- plugin
|
||||
- regression
|
||||
dependencies: []
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Recent `plugin/subminer.lua` change added an IPC readiness gate in `start_overlay()` that rejects start when no SubMiner process exists yet. This blocks manual start (`y-s`) and script-message start even when binary path overrides are valid (`SUBMINER_APPIMAGE_PATH`, `SUBMINER_BINARY_PATH`, `binary_path`).
|
||||
|
||||
Need to restore expected behavior:
|
||||
- allow cold start when process is not running,
|
||||
- preserve mismatch/misconfiguration checks when process is already running,
|
||||
- add regression coverage for the new start gate behavior.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Action Steps
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Add failing regression test covering cold-start path from plugin start gate.
|
||||
2. Patch `start_overlay()` gate logic so process-not-running does not block initial start.
|
||||
3. Keep existing IPC mismatch/missing-socket checks for running-process path.
|
||||
4. Re-run plugin validation and focused tests.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 `y-s` / `subminer-start` can launch overlay when SubMiner is not yet running and binary is available.
|
||||
- [x] #2 Plugin still surfaces clear refusal for true IPC/socket mismatches after process is running.
|
||||
- [x] #3 Regression test exists and fails before fix / passes after fix.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
- Patched `plugin/subminer.lua` start gate: `start_overlay()` now treats `SubMiner process not running` as a valid cold-start condition and only refuses start for other IPC readiness failures.
|
||||
- Added regression harness `scripts/test-plugin-start-gate.lua` to load plugin with mocked mpv APIs and assert `subminer-start` triggers `--start` command for cold-start path.
|
||||
- Validation:
|
||||
- red: `SUBMINER_APPIMAGE_PATH=/tmp/subminer-binary lua scripts/test-plugin-start-gate.lua` (failed before patch with expected cold-start assertion),
|
||||
- green: same command passes after patch,
|
||||
- `luac -p plugin/subminer.lua`,
|
||||
- `bun test launcher/mpv.test.ts`.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Fixed mpv plugin start regression by allowing cold-start launch when SubMiner process is not yet running, while preserving existing refusal path for non-cold-start IPC readiness errors. Added an executable Lua regression harness for this path and validated with syntax + focused launcher MPV tests.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -5,6 +5,7 @@
|
||||
* 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.
|
||||
@@ -23,7 +24,7 @@
|
||||
// Control whether browser opens automatically for texthooker.
|
||||
// ==========================================
|
||||
"texthooker": {
|
||||
"openBrowser": true, // Open browser setting. Values: true | false
|
||||
"openBrowser": true // Open browser setting. Values: true | false
|
||||
}, // Control whether browser opens automatically for texthooker.
|
||||
|
||||
// ==========================================
|
||||
@@ -33,7 +34,7 @@
|
||||
// ==========================================
|
||||
"websocket": {
|
||||
"enabled": "auto", // Built-in subtitle websocket server mode. Values: auto | true | false
|
||||
"port": 6677, // Built-in subtitle websocket server port.
|
||||
"port": 6677 // Built-in subtitle websocket server port.
|
||||
}, // Built-in WebSocket server broadcasts subtitle text to connected clients.
|
||||
|
||||
// ==========================================
|
||||
@@ -42,7 +43,7 @@
|
||||
// Set to debug for full runtime diagnostics.
|
||||
// ==========================================
|
||||
"logging": {
|
||||
"level": "info", // Minimum log level for runtime logging. Values: debug | info | warn | error
|
||||
"level": "info" // Minimum log level for runtime logging. Values: debug | info | warn | error
|
||||
}, // Controls logging verbosity.
|
||||
|
||||
// ==========================================
|
||||
@@ -64,7 +65,7 @@
|
||||
"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.
|
||||
"openJimaku": "Ctrl+Shift+J" // Open jimaku setting.
|
||||
}, // Overlay keyboard shortcuts. Set a shortcut to null to disable.
|
||||
|
||||
// ==========================================
|
||||
@@ -74,7 +75,7 @@
|
||||
// This edit-mode shortcut is fixed and is not currently configurable.
|
||||
// ==========================================
|
||||
"invisibleOverlay": {
|
||||
"startupVisibility": "platform-default", // Startup visibility setting.
|
||||
"startupVisibility": "platform-default" // Startup visibility setting.
|
||||
}, // Startup behavior for the invisible interactive subtitle mining layer.
|
||||
|
||||
// ==========================================
|
||||
@@ -94,7 +95,7 @@
|
||||
"secondarySub": {
|
||||
"secondarySubLanguages": [], // Secondary sub languages setting.
|
||||
"autoLoadSecondarySub": false, // Auto load secondary sub setting. Values: true | false
|
||||
"defaultMode": "hover", // Default mode setting.
|
||||
"defaultMode": "hover" // Default mode setting.
|
||||
}, // Dual subtitle track options.
|
||||
|
||||
// ==========================================
|
||||
@@ -105,7 +106,7 @@
|
||||
"defaultMode": "auto", // Subsync default mode. Values: auto | manual
|
||||
"alass_path": "", // Alass path setting.
|
||||
"ffsubsync_path": "", // Ffsubsync path setting.
|
||||
"ffmpeg_path": "", // Ffmpeg path setting.
|
||||
"ffmpeg_path": "" // Ffmpeg path setting.
|
||||
}, // Subsync engine and executable paths.
|
||||
|
||||
// ==========================================
|
||||
@@ -113,7 +114,7 @@
|
||||
// Initial vertical subtitle position from the bottom.
|
||||
// ==========================================
|
||||
"subtitlePosition": {
|
||||
"yPercent": 10, // Y percent setting.
|
||||
"yPercent": 10 // Y percent setting.
|
||||
}, // Initial vertical subtitle position from the bottom.
|
||||
|
||||
// ==========================================
|
||||
@@ -138,7 +139,7 @@
|
||||
"N2": "#f5a97f", // N2 setting.
|
||||
"N3": "#f9e2af", // N3 setting.
|
||||
"N4": "#a6e3a1", // N4 setting.
|
||||
"N5": "#8aadf4", // N5 setting.
|
||||
"N5": "#8aadf4" // N5 setting.
|
||||
}, // Jlpt colors setting.
|
||||
"frequencyDictionary": {
|
||||
"enabled": false, // Enable frequency-dictionary-based highlighting based on token rank. Values: true | false
|
||||
@@ -146,7 +147,13 @@
|
||||
"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).
|
||||
"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, // Font size setting.
|
||||
@@ -154,8 +161,8 @@
|
||||
"backgroundColor": "transparent", // Background color setting.
|
||||
"fontWeight": "normal", // Font weight setting.
|
||||
"fontStyle": "normal", // Font style setting.
|
||||
"fontFamily": "M PLUS 1, Noto Sans CJK JP Regular, Noto Sans CJK JP, Hiragino Sans, Hiragino Kaku Gothic ProN, Yu Gothic, Arial Unicode MS, Arial, sans-serif", // Font family setting.
|
||||
}, // Secondary setting.
|
||||
"fontFamily": "M PLUS 1, Noto Sans CJK JP Regular, Noto Sans CJK JP, Hiragino Sans, Hiragino Kaku Gothic ProN, Yu Gothic, Arial Unicode MS, Arial, sans-serif" // Font family setting.
|
||||
} // Secondary setting.
|
||||
}, // Primary and secondary subtitle styling.
|
||||
|
||||
// ==========================================
|
||||
@@ -168,13 +175,15 @@
|
||||
"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.
|
||||
"tags": [
|
||||
"SubMiner"
|
||||
], // Tags to add to cards mined or updated by SubMiner. Provide an empty array to disable automatic tagging.
|
||||
"fields": {
|
||||
"audio": "ExpressionAudio", // Audio setting.
|
||||
"image": "Picture", // Image setting.
|
||||
"sentence": "Sentence", // Sentence setting.
|
||||
"miscInfo": "MiscInfo", // Misc info setting.
|
||||
"translation": "SelectionText", // Translation setting.
|
||||
"translation": "SelectionText" // Translation setting.
|
||||
}, // Fields setting.
|
||||
"ai": {
|
||||
"enabled": false, // Enabled setting. Values: true | false
|
||||
@@ -183,7 +192,7 @@
|
||||
"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.
|
||||
"systemPrompt": "You are a translation engine. Return only the translated text with no explanations." // System prompt setting.
|
||||
}, // Ai setting.
|
||||
"media": {
|
||||
"generateAudio": true, // Generate audio setting. Values: true | false
|
||||
@@ -196,7 +205,7 @@
|
||||
"animatedCrf": 35, // Animated crf setting.
|
||||
"audioPadding": 0.5, // Audio padding setting.
|
||||
"fallbackDuration": 3, // Fallback duration setting.
|
||||
"maxMediaDuration": 30, // Max media duration setting.
|
||||
"maxMediaDuration": 30 // Max media duration setting.
|
||||
}, // Media setting.
|
||||
"behavior": {
|
||||
"overwriteAudio": true, // Overwrite audio setting. Values: true | false
|
||||
@@ -204,7 +213,7 @@
|
||||
"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
|
||||
"autoUpdateNewCards": true // Automatically update newly added cards. Values: true | false
|
||||
}, // Behavior setting.
|
||||
"nPlusOne": {
|
||||
"highlightEnabled": false, // Enable fast local highlighting for words already known in Anki. Values: true | false
|
||||
@@ -213,20 +222,20 @@
|
||||
"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.
|
||||
"knownWord": "#a6da95" // Color used for legacy known-word highlights.
|
||||
}, // N plus one setting.
|
||||
"metadata": {
|
||||
"pattern": "[SubMiner] %f (%t)", // Pattern setting.
|
||||
"pattern": "[SubMiner] %f (%t)" // Pattern setting.
|
||||
}, // Metadata setting.
|
||||
"isLapis": {
|
||||
"enabled": false, // Enabled setting. Values: true | false
|
||||
"sentenceCardModel": "Japanese sentences", // Sentence card model setting.
|
||||
"sentenceCardModel": "Japanese sentences" // Sentence card model setting.
|
||||
}, // Is lapis setting.
|
||||
"isKiku": {
|
||||
"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.
|
||||
"deleteDuplicateInAuto": true // Delete duplicate in auto setting. Values: true | false
|
||||
} // Is kiku setting.
|
||||
}, // Automatic Anki updates and media generation options.
|
||||
|
||||
// ==========================================
|
||||
@@ -236,7 +245,7 @@
|
||||
"jimaku": {
|
||||
"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.
|
||||
"maxEntryResults": 10 // Maximum Jimaku search results returned.
|
||||
}, // Jimaku API configuration and defaults.
|
||||
|
||||
// ==========================================
|
||||
@@ -247,7 +256,10 @@
|
||||
"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.
|
||||
"primarySubLanguages": [
|
||||
"ja",
|
||||
"jpn"
|
||||
] // Comma-separated primary subtitle language priority used by the launcher.
|
||||
}, // Defaults for subminer YouTube subtitle extraction/transcription mode.
|
||||
|
||||
// ==========================================
|
||||
@@ -256,7 +268,7 @@
|
||||
// ==========================================
|
||||
"anilist": {
|
||||
"enabled": false, // Enable AniList post-watch progress updates. Values: true | false
|
||||
"accessToken": "", // Optional explicit AniList access token override; leave empty to use locally stored token from setup.
|
||||
"accessToken": "" // Optional explicit AniList access token override; leave empty to use locally stored token from setup.
|
||||
}, // Anilist API credentials and update behavior.
|
||||
|
||||
// ==========================================
|
||||
@@ -280,8 +292,16 @@
|
||||
"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.
|
||||
"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.
|
||||
|
||||
// ==========================================
|
||||
@@ -292,7 +312,7 @@
|
||||
"discordPresence": {
|
||||
"enabled": false, // Enable optional Discord Rich Presence updates. Values: true | false
|
||||
"updateIntervalMs": 3000, // Minimum interval between presence payload updates.
|
||||
"debounceMs": 750, // Debounce delay used to collapse bursty presence updates.
|
||||
"debounceMs": 750 // Debounce delay used to collapse bursty presence updates.
|
||||
}, // Optional Discord Rich Presence activity card updates for current playback/study session.
|
||||
|
||||
// ==========================================
|
||||
@@ -314,7 +334,7 @@
|
||||
"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.
|
||||
"vacuumIntervalDays": 7 // Minimum days between VACUUM runs.
|
||||
} // Retention setting.
|
||||
} // Enable/disable immersion tracking.
|
||||
}
|
||||
|
||||
@@ -125,65 +125,108 @@ src/renderer/
|
||||
|
||||
## Flow Diagram
|
||||
|
||||
The main process has three layers: `main.ts` delegates to composition modules that wire together domain services. The renderer runs in a separate Electron process, connected through `preload.ts`.
|
||||
The main process has three layers: `main.ts` delegates to composition modules that wire together domain services. Three overlay windows (visible, invisible, secondary) run in separate Electron renderer processes, connected through `preload.ts`. External runtimes (launcher CLI and mpv plugin) operate independently and communicate via IPC socket or CLI passthrough.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
classDef entry fill:#c6a0f6,stroke:#363a4f,color:#24273a,stroke-width:2px
|
||||
classDef comp fill:#b7bdf8,stroke:#363a4f,color:#24273a,stroke-width:1.5px
|
||||
classDef svc fill:#8aadf4,stroke:#363a4f,color:#24273a,stroke-width:1.5px
|
||||
classDef bridge fill:#f5a97f,stroke:#363a4f,color:#24273a,stroke-width:1.5px
|
||||
classDef rend fill:#8bd5ca,stroke:#363a4f,color:#24273a,stroke-width:1.5px
|
||||
classDef ext fill:#a6da95,stroke:#363a4f,color:#24273a,stroke-width:1.5px
|
||||
classDef entry fill:#c6a0f6,stroke:#494d64,color:#24273a,stroke-width:2px,font-weight:bold
|
||||
classDef comp fill:#b7bdf8,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef svc fill:#8aadf4,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef bridge fill:#f5a97f,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef rend fill:#8bd5ca,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef ext fill:#a6da95,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef extrt fill:#eed49f,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
|
||||
Main["main.ts"]:::entry
|
||||
Main["main.ts — composition root"]:::entry
|
||||
|
||||
subgraph Comp["Composition — src/main/"]
|
||||
Startup["Startup & Lifecycle<br/>startup · app-lifecycle<br/>startup-lifecycle · state"]:::comp
|
||||
Wiring["Runtime Wiring<br/>ipc-runtime · cli-runtime<br/>overlay-runtime · subsync-runtime"]:::comp
|
||||
direction TB
|
||||
Startup["Startup & Lifecycle<br/>startup · app-lifecycle · startup-lifecycle · state"]:::comp
|
||||
Wiring["Runtime Wiring<br/>ipc-runtime · cli-runtime · overlay-runtime · subsync-runtime"]:::comp
|
||||
Composers["Composers<br/>mpv-runtime · anilist-tracking · jellyfin-runtime"]:::comp
|
||||
end
|
||||
|
||||
subgraph Svc["Services — src/core/services/"]
|
||||
direction TB
|
||||
subgraph SvcRow1[" "]
|
||||
direction LR
|
||||
Mpv["MPV Stack<br/>transport · protocol<br/>properties · render-metrics"]:::svc
|
||||
Overlay["Overlay Manager<br/>window · geometry<br/>visibility · bridge"]:::svc
|
||||
end
|
||||
subgraph SvcRow2[" "]
|
||||
direction LR
|
||||
Mpv["MPV Stack<br/>transport · protocol<br/>state · properties"]:::svc
|
||||
Overlay["Overlay<br/>manager · window<br/>visibility · bridge"]:::svc
|
||||
Mining["Mining & Subtitles<br/>mining · field-grouping<br/>subtitle-ws · tokenizer"]:::svc
|
||||
Integrations["Integrations<br/>jimaku · subsync<br/>texthooker · yomitan"]:::svc
|
||||
Integrations["Integrations<br/>jimaku · subsync · texthooker<br/>yomitan · discord-presence"]:::svc
|
||||
end
|
||||
subgraph SvcRow3[" "]
|
||||
direction LR
|
||||
Tracking["Tracking<br/>anilist · jellyfin-remote<br/>immersion-tracker"]:::svc
|
||||
Config["Config & Runtime<br/>config-hot-reload<br/>runtime-options"]:::svc
|
||||
end
|
||||
end
|
||||
|
||||
Bridge(["preload.ts — Electron IPC"]):::bridge
|
||||
Bridge(["preload.ts — Electron IPC bridge"]):::bridge
|
||||
|
||||
subgraph Rend["Renderer — src/renderer/"]
|
||||
Orchestration["renderer.ts<br/>orchestration · IPC wiring"]:::rend
|
||||
UI["subtitle-render · positioning<br/>handlers · modals"]:::rend
|
||||
direction TB
|
||||
subgraph Windows["Three overlay windows"]
|
||||
direction LR
|
||||
Visible["Visible<br/>interactive Yomitan lookups"]:::rend
|
||||
Invisible["Invisible<br/>mpv-matched positioning"]:::rend
|
||||
Secondary["Secondary<br/>secondary subtitle bar"]:::rend
|
||||
end
|
||||
UI["subtitle-render · positioning · handlers · modals"]:::rend
|
||||
end
|
||||
|
||||
subgraph Ext["External Systems"]
|
||||
direction LR
|
||||
mpv["mpv"]:::ext
|
||||
Anki["AnkiConnect"]:::ext
|
||||
Jimaku["Jimaku API"]:::ext
|
||||
Tracker["Window Tracker"]:::ext
|
||||
mpvExt["mpv player"]:::ext
|
||||
AnkiExt["AnkiConnect"]:::ext
|
||||
JimakuExt["Jimaku API"]:::ext
|
||||
TrackerExt["Window Tracker<br/>Hyprland · Sway · X11 · macOS"]:::ext
|
||||
AnilistExt["AniList API"]:::ext
|
||||
JellyfinExt["Jellyfin"]:::ext
|
||||
DiscordExt["Discord RPC"]:::ext
|
||||
end
|
||||
|
||||
Main -->|delegates| Comp
|
||||
Startup -->|initializes| Svc
|
||||
Wiring -->|dispatches to| Svc
|
||||
subgraph ExtRt["External Runtimes"]
|
||||
direction LR
|
||||
Launcher["launcher/<br/>CLI command dispatch"]:::extrt
|
||||
Plugin["subminer.lua<br/>mpv plugin"]:::extrt
|
||||
end
|
||||
|
||||
Main -->|"delegates"| Comp
|
||||
Startup -->|"initializes"| Svc
|
||||
Wiring -->|"dispatches to"| Svc
|
||||
Composers -->|"wires"| Svc
|
||||
|
||||
Overlay <-->Bridge
|
||||
Mining <--> Bridge
|
||||
Bridge <--> Orchestration
|
||||
Orchestration --> UI
|
||||
Bridge <--> Visible
|
||||
Bridge <--> Invisible
|
||||
Bridge <--> Secondary
|
||||
Windows --> UI
|
||||
|
||||
Mpv <-->|JSON socket| mpv
|
||||
Mining -->|HTTP| Anki
|
||||
Integrations -->|HTTP| Jimaku
|
||||
Overlay --> Tracker
|
||||
Mpv <-->|"JSON IPC socket"| mpvExt
|
||||
Mining -->|"HTTP"| AnkiExt
|
||||
Integrations -->|"HTTP"| JimakuExt
|
||||
Overlay -->|"platform API"| TrackerExt
|
||||
Tracking -->|"HTTP"| AnilistExt
|
||||
Tracking -->|"HTTP"| JellyfinExt
|
||||
Integrations -->|"RPC"| DiscordExt
|
||||
|
||||
Launcher -->|"CLI passthrough"| Main
|
||||
Plugin -->|"IPC socket"| mpvExt
|
||||
|
||||
style Comp fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
style Svc fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
style SvcRow1 fill:transparent,stroke:none
|
||||
style SvcRow2 fill:transparent,stroke:none
|
||||
style SvcRow3 fill:transparent,stroke:none
|
||||
style Rend fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
style Windows fill:#1e2030,stroke:#494d64,color:#cad3f5
|
||||
style Ext fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
style ExtRt fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
```
|
||||
|
||||
## Composition Pattern
|
||||
@@ -242,52 +285,81 @@ For domains migrated to reducer-style transitions (for example AniList token/que
|
||||
|
||||
## Program Lifecycle
|
||||
|
||||
- **Startup:** `startup.ts` parses CLI args and detects the compositor backend. If `--generate-config` is passed, it writes the template and exits. Otherwise `app-lifecycle.ts` acquires the single-instance lock and registers Electron lifecycle hooks.
|
||||
- **Initialization:** Once `app.whenReady()` fires, `composeAppReadyRuntime()` runs the critical path first (strict config reload, runtime options + keybindings, mpv client creation, overlay/IPC setup). Non-critical warmups are launched asynchronously (`mecab`, `yomitan-extension`, dictionary prewarm, optional Jellyfin remote session).
|
||||
- **Runtime:** Event-driven. mpv events, IPC messages, CLI commands, overlay shortcuts, hot-reload notifications, and integration callbacks route through runtime handlers/composers, update `AppState`, and broadcast to overlay windows.
|
||||
- **Overlay window model:** runtime manages three overlay windows: `visible`, `invisible`, and `secondary`. `splitOverlayGeometryForSecondaryBar()` reserves the top 20% for the secondary subtitle bar and routes the remaining area to the active primary overlay layer.
|
||||
- **Shutdown:** `onWillQuitCleanup` tears down tray + watchers + integrations, stops subtitle/texthooker servers, flushes buffered MPV OSD log writes, closes token/session windows, and stops Jellyfin/Discord runtime services.
|
||||
- **Module-level init:** Before `app.ready`, the composition root registers protocols, sets platform flags, constructs all services, and wires dependency injection. `runAndApplyStartupState()` parses CLI args and detects the compositor backend.
|
||||
- **Startup:** If `--generate-config` is passed, it writes the template and exits. Otherwise `app-lifecycle.ts` acquires the single-instance lock and registers Electron lifecycle hooks.
|
||||
- **Critical-path init:** Once `app.whenReady()` fires, `composeAppReadyRuntime()` runs strict config reload, resolves keybindings, creates the `MpvIpcClient` (which immediately connects and subscribes to 26 properties), and initializes the `RuntimeOptionsManager`, `SubtitleTimingTracker`, and `ImmersionTrackerService`.
|
||||
- **Overlay runtime:** `initializeOverlayRuntime()` creates three overlay windows — **visible** (interactive Yomitan lookups), **invisible** (mpv-matched subtitle positioning), and **secondary** (secondary subtitle bar, top 20% via `splitOverlayGeometryForSecondaryBar`) — then registers global shortcuts and sets initial bounds from the window tracker.
|
||||
- **Background warmups:** Non-critical services are launched asynchronously: MeCab tokenizer check, Yomitan extension load, JLPT + frequency dictionary prewarm, optional Jellyfin remote session, Discord presence service, and AniList token refresh.
|
||||
- **Runtime:** Event-driven. mpv property changes, IPC messages, CLI commands, overlay shortcuts, and hot-reload notifications route through runtime handlers/composers. Subtitle text flows through `SubtitlePipeline` (normalize → tokenize → merge), and results broadcast to all overlay windows.
|
||||
- **Shutdown:** `onWillQuitCleanup` destroys tray + config watcher, unregisters shortcuts, stops WebSocket + texthooker servers, closes the mpv socket + flushes OSD log, stops the window tracker, closes the Yomitan parser window, flushes the immersion tracker (SQLite), stops Jellyfin/Discord services, and cleans Anki/AniList state.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
classDef start fill:#c6a0f6,stroke:#363a4f,color:#24273a,stroke-width:2px
|
||||
classDef phase fill:#b7bdf8,stroke:#363a4f,color:#24273a,stroke-width:1.5px
|
||||
classDef decision fill:#f5a97f,stroke:#363a4f,color:#24273a,stroke-width:1.5px
|
||||
classDef init fill:#8aadf4,stroke:#363a4f,color:#24273a,stroke-width:1.5px
|
||||
classDef runtime fill:#8bd5ca,stroke:#363a4f,color:#24273a,stroke-width:1.5px
|
||||
classDef shutdown fill:#ed8796,stroke:#363a4f,color:#24273a,stroke-width:1.5px
|
||||
flowchart LR
|
||||
classDef start fill:#c6a0f6,stroke:#494d64,color:#24273a,stroke-width:2px,font-weight:bold
|
||||
classDef phase fill:#b7bdf8,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef decision fill:#f5a97f,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef init fill:#8aadf4,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef runtime fill:#8bd5ca,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef shutdown fill:#ed8796,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef warmup fill:#eed49f,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
|
||||
CLI["CLI args & environment"]:::start
|
||||
CLI --> Parse["startup.ts<br/>Parse argv · detect backend · resolve config"]:::phase
|
||||
Parse --> GenCheck{"--generate-config?"}:::decision
|
||||
GenCheck -->|yes| GenExit["Write config template & exit"]:::phase
|
||||
GenCheck -->|no| Lifecycle["app-lifecycle.ts<br/>Acquire single-instance lock<br/>Register Electron lifecycle hooks"]:::phase
|
||||
Lifecycle -->|"app.whenReady()"| Ready["startup-lifecycle.ts"]:::phase
|
||||
CLI["CLI args &<br/>environment"]:::start
|
||||
CLI --> Proto["Module-level init<br/>register protocols<br/>construct services<br/>wire deps"]:::phase
|
||||
Proto --> Parse["startup.ts<br/>parse argv<br/>detect backend"]:::phase
|
||||
Parse --> GenCheck{"--generate<br/>-config?"}:::decision
|
||||
GenCheck -->|"yes"| GenExit["Write template<br/>& exit"]:::phase
|
||||
GenCheck -->|"no"| Lock["app-lifecycle.ts<br/>single-instance lock<br/>lifecycle hooks"]:::phase
|
||||
|
||||
Ready --> Init
|
||||
subgraph Init["Initialization"]
|
||||
direction LR
|
||||
Config["Load config<br/>resolve keybindings"]:::init
|
||||
Runtime["Create mpv client<br/>init runtime options"]:::init
|
||||
Platform["Start window tracker<br/>WebSocket policy"]:::init
|
||||
end
|
||||
Lock -->|"app.whenReady()"| Ready["composeAppReady<br/>Runtime()"]:::phase
|
||||
|
||||
Init --> Create["Create overlay window<br/>Establish IPC bridge"]:::phase
|
||||
Create --> Warm["Background warmups<br/>MeCab · Yomitan · dictionaries · Jellyfin"]:::phase
|
||||
Ready --> Config["Config reload<br/>keybindings<br/>log level"]:::init
|
||||
Ready --> MpvInit["MpvIpcClient<br/>connect socket<br/>subscribe 26 props"]:::init
|
||||
Ready --> Platform["RuntimeOptions<br/>timing tracker<br/>immersion tracker"]:::init
|
||||
|
||||
Config --> OverlayInit
|
||||
MpvInit --> OverlayInit
|
||||
Platform --> OverlayInit
|
||||
|
||||
OverlayInit["initializeOverlay<br/>Runtime()"]:::phase
|
||||
|
||||
OverlayInit --> VisWin["Visible window<br/>Yomitan lookups"]:::init
|
||||
OverlayInit --> InvWin["Invisible window<br/>mpv positioning"]:::init
|
||||
OverlayInit --> SecWin["Secondary window<br/>subtitle bar"]:::init
|
||||
OverlayInit --> Shortcuts["Register global<br/>shortcuts"]:::init
|
||||
|
||||
VisWin --> Warmups
|
||||
InvWin --> Warmups
|
||||
SecWin --> Warmups
|
||||
Shortcuts --> Warmups
|
||||
|
||||
Warmups["Background<br/>warmups"]:::phase
|
||||
|
||||
Warmups --> W1["MeCab"]:::warmup
|
||||
Warmups --> W2["Yomitan"]:::warmup
|
||||
Warmups --> W3["JLPT + freq<br/>dictionaries"]:::warmup
|
||||
Warmups --> W4["Jellyfin"]:::warmup
|
||||
Warmups --> W5["Discord"]:::warmup
|
||||
Warmups --> W6["AniList"]:::warmup
|
||||
|
||||
W1 & W2 & W3 & W4 & W5 & W6 --> Loop
|
||||
|
||||
Warm --> Loop
|
||||
subgraph Loop["Runtime — event-driven"]
|
||||
direction LR
|
||||
Events["mpv · IPC · CLI<br/>shortcut events"]:::runtime
|
||||
Dispatch["Route to service<br/>via composition layer"]:::runtime
|
||||
State["Update state<br/>broadcast to renderer"]:::runtime
|
||||
Events --> Dispatch --> State
|
||||
direction TB
|
||||
MpvEvt["mpv events: subtitle · timing · metrics"]:::runtime
|
||||
IpcEvt["IPC: renderer requests · CLI commands"]:::runtime
|
||||
ExtEvt["Shortcuts · config hot-reload"]:::runtime
|
||||
MpvEvt & IpcEvt & ExtEvt --> Route["Route via composers"]:::runtime
|
||||
Route --> Process["SubtitlePipeline<br/>normalize → tokenize → merge"]:::runtime
|
||||
Process --> Broadcast["Update AppState<br/>broadcast to windows"]:::runtime
|
||||
end
|
||||
|
||||
Loop -->|"app close"| Quit["Electron will-quit"]:::shutdown
|
||||
Quit --> Teardown["Close mpv socket · unregister shortcuts<br/>Stop WebSocket & texthooker<br/>Destroy tracker · clean Anki state"]:::shutdown
|
||||
Loop -->|"quit signal"| Quit["will-quit"]:::shutdown
|
||||
|
||||
Quit --> T1["Tray · config watcher<br/>global shortcuts"]:::shutdown
|
||||
Quit --> T2["WebSocket · texthooker<br/>mpv socket · OSD log"]:::shutdown
|
||||
Quit --> T3["Window tracker<br/>Yomitan parser"]:::shutdown
|
||||
Quit --> T4["Immersion tracker<br/>Jellyfin · Discord<br/>Anki · AniList"]:::shutdown
|
||||
|
||||
style Init fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
style Loop fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
```
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* 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.
|
||||
@@ -23,7 +24,7 @@
|
||||
// Control whether browser opens automatically for texthooker.
|
||||
// ==========================================
|
||||
"texthooker": {
|
||||
"openBrowser": true, // Open browser setting. Values: true | false
|
||||
"openBrowser": true // Open browser setting. Values: true | false
|
||||
}, // Control whether browser opens automatically for texthooker.
|
||||
|
||||
// ==========================================
|
||||
@@ -33,7 +34,7 @@
|
||||
// ==========================================
|
||||
"websocket": {
|
||||
"enabled": "auto", // Built-in subtitle websocket server mode. Values: auto | true | false
|
||||
"port": 6677, // Built-in subtitle websocket server port.
|
||||
"port": 6677 // Built-in subtitle websocket server port.
|
||||
}, // Built-in WebSocket server broadcasts subtitle text to connected clients.
|
||||
|
||||
// ==========================================
|
||||
@@ -42,7 +43,7 @@
|
||||
// Set to debug for full runtime diagnostics.
|
||||
// ==========================================
|
||||
"logging": {
|
||||
"level": "info", // Minimum log level for runtime logging. Values: debug | info | warn | error
|
||||
"level": "info" // Minimum log level for runtime logging. Values: debug | info | warn | error
|
||||
}, // Controls logging verbosity.
|
||||
|
||||
// ==========================================
|
||||
@@ -64,7 +65,7 @@
|
||||
"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.
|
||||
"openJimaku": "Ctrl+Shift+J" // Open jimaku setting.
|
||||
}, // Overlay keyboard shortcuts. Set a shortcut to null to disable.
|
||||
|
||||
// ==========================================
|
||||
@@ -74,7 +75,7 @@
|
||||
// This edit-mode shortcut is fixed and is not currently configurable.
|
||||
// ==========================================
|
||||
"invisibleOverlay": {
|
||||
"startupVisibility": "platform-default", // Startup visibility setting.
|
||||
"startupVisibility": "platform-default" // Startup visibility setting.
|
||||
}, // Startup behavior for the invisible interactive subtitle mining layer.
|
||||
|
||||
// ==========================================
|
||||
@@ -94,7 +95,7 @@
|
||||
"secondarySub": {
|
||||
"secondarySubLanguages": [], // Secondary sub languages setting.
|
||||
"autoLoadSecondarySub": false, // Auto load secondary sub setting. Values: true | false
|
||||
"defaultMode": "hover", // Default mode setting.
|
||||
"defaultMode": "hover" // Default mode setting.
|
||||
}, // Dual subtitle track options.
|
||||
|
||||
// ==========================================
|
||||
@@ -105,7 +106,7 @@
|
||||
"defaultMode": "auto", // Subsync default mode. Values: auto | manual
|
||||
"alass_path": "", // Alass path setting.
|
||||
"ffsubsync_path": "", // Ffsubsync path setting.
|
||||
"ffmpeg_path": "", // Ffmpeg path setting.
|
||||
"ffmpeg_path": "" // Ffmpeg path setting.
|
||||
}, // Subsync engine and executable paths.
|
||||
|
||||
// ==========================================
|
||||
@@ -113,7 +114,7 @@
|
||||
// Initial vertical subtitle position from the bottom.
|
||||
// ==========================================
|
||||
"subtitlePosition": {
|
||||
"yPercent": 10, // Y percent setting.
|
||||
"yPercent": 10 // Y percent setting.
|
||||
}, // Initial vertical subtitle position from the bottom.
|
||||
|
||||
// ==========================================
|
||||
@@ -138,7 +139,7 @@
|
||||
"N2": "#f5a97f", // N2 setting.
|
||||
"N3": "#f9e2af", // N3 setting.
|
||||
"N4": "#a6e3a1", // N4 setting.
|
||||
"N5": "#8aadf4", // N5 setting.
|
||||
"N5": "#8aadf4" // N5 setting.
|
||||
}, // Jlpt colors setting.
|
||||
"frequencyDictionary": {
|
||||
"enabled": false, // Enable frequency-dictionary-based highlighting based on token rank. Values: true | false
|
||||
@@ -146,7 +147,13 @@
|
||||
"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).
|
||||
"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, // Font size setting.
|
||||
@@ -154,8 +161,8 @@
|
||||
"backgroundColor": "transparent", // Background color setting.
|
||||
"fontWeight": "normal", // Font weight setting.
|
||||
"fontStyle": "normal", // Font style setting.
|
||||
"fontFamily": "M PLUS 1, Noto Sans CJK JP Regular, Noto Sans CJK JP, Hiragino Sans, Hiragino Kaku Gothic ProN, Yu Gothic, Arial Unicode MS, Arial, sans-serif", // Font family setting.
|
||||
}, // Secondary setting.
|
||||
"fontFamily": "M PLUS 1, Noto Sans CJK JP Regular, Noto Sans CJK JP, Hiragino Sans, Hiragino Kaku Gothic ProN, Yu Gothic, Arial Unicode MS, Arial, sans-serif" // Font family setting.
|
||||
} // Secondary setting.
|
||||
}, // Primary and secondary subtitle styling.
|
||||
|
||||
// ==========================================
|
||||
@@ -168,13 +175,15 @@
|
||||
"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.
|
||||
"tags": [
|
||||
"SubMiner"
|
||||
], // Tags to add to cards mined or updated by SubMiner. Provide an empty array to disable automatic tagging.
|
||||
"fields": {
|
||||
"audio": "ExpressionAudio", // Audio setting.
|
||||
"image": "Picture", // Image setting.
|
||||
"sentence": "Sentence", // Sentence setting.
|
||||
"miscInfo": "MiscInfo", // Misc info setting.
|
||||
"translation": "SelectionText", // Translation setting.
|
||||
"translation": "SelectionText" // Translation setting.
|
||||
}, // Fields setting.
|
||||
"ai": {
|
||||
"enabled": false, // Enabled setting. Values: true | false
|
||||
@@ -183,7 +192,7 @@
|
||||
"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.
|
||||
"systemPrompt": "You are a translation engine. Return only the translated text with no explanations." // System prompt setting.
|
||||
}, // Ai setting.
|
||||
"media": {
|
||||
"generateAudio": true, // Generate audio setting. Values: true | false
|
||||
@@ -196,7 +205,7 @@
|
||||
"animatedCrf": 35, // Animated crf setting.
|
||||
"audioPadding": 0.5, // Audio padding setting.
|
||||
"fallbackDuration": 3, // Fallback duration setting.
|
||||
"maxMediaDuration": 30, // Max media duration setting.
|
||||
"maxMediaDuration": 30 // Max media duration setting.
|
||||
}, // Media setting.
|
||||
"behavior": {
|
||||
"overwriteAudio": true, // Overwrite audio setting. Values: true | false
|
||||
@@ -204,7 +213,7 @@
|
||||
"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
|
||||
"autoUpdateNewCards": true // Automatically update newly added cards. Values: true | false
|
||||
}, // Behavior setting.
|
||||
"nPlusOne": {
|
||||
"highlightEnabled": false, // Enable fast local highlighting for words already known in Anki. Values: true | false
|
||||
@@ -213,20 +222,20 @@
|
||||
"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.
|
||||
"knownWord": "#a6da95" // Color used for legacy known-word highlights.
|
||||
}, // N plus one setting.
|
||||
"metadata": {
|
||||
"pattern": "[SubMiner] %f (%t)", // Pattern setting.
|
||||
"pattern": "[SubMiner] %f (%t)" // Pattern setting.
|
||||
}, // Metadata setting.
|
||||
"isLapis": {
|
||||
"enabled": false, // Enabled setting. Values: true | false
|
||||
"sentenceCardModel": "Japanese sentences", // Sentence card model setting.
|
||||
"sentenceCardModel": "Japanese sentences" // Sentence card model setting.
|
||||
}, // Is lapis setting.
|
||||
"isKiku": {
|
||||
"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.
|
||||
"deleteDuplicateInAuto": true // Delete duplicate in auto setting. Values: true | false
|
||||
} // Is kiku setting.
|
||||
}, // Automatic Anki updates and media generation options.
|
||||
|
||||
// ==========================================
|
||||
@@ -236,7 +245,7 @@
|
||||
"jimaku": {
|
||||
"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.
|
||||
"maxEntryResults": 10 // Maximum Jimaku search results returned.
|
||||
}, // Jimaku API configuration and defaults.
|
||||
|
||||
// ==========================================
|
||||
@@ -247,7 +256,10 @@
|
||||
"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.
|
||||
"primarySubLanguages": [
|
||||
"ja",
|
||||
"jpn"
|
||||
] // Comma-separated primary subtitle language priority used by the launcher.
|
||||
}, // Defaults for subminer YouTube subtitle extraction/transcription mode.
|
||||
|
||||
// ==========================================
|
||||
@@ -256,7 +268,7 @@
|
||||
// ==========================================
|
||||
"anilist": {
|
||||
"enabled": false, // Enable AniList post-watch progress updates. Values: true | false
|
||||
"accessToken": "", // Optional explicit AniList access token override; leave empty to use locally stored token from setup.
|
||||
"accessToken": "" // Optional explicit AniList access token override; leave empty to use locally stored token from setup.
|
||||
}, // Anilist API credentials and update behavior.
|
||||
|
||||
// ==========================================
|
||||
@@ -280,8 +292,16 @@
|
||||
"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.
|
||||
"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.
|
||||
|
||||
// ==========================================
|
||||
@@ -292,7 +312,7 @@
|
||||
"discordPresence": {
|
||||
"enabled": false, // Enable optional Discord Rich Presence updates. Values: true | false
|
||||
"updateIntervalMs": 3000, // Minimum interval between presence payload updates.
|
||||
"debounceMs": 750, // Debounce delay used to collapse bursty presence updates.
|
||||
"debounceMs": 750 // Debounce delay used to collapse bursty presence updates.
|
||||
}, // Optional Discord Rich Presence activity card updates for current playback/study session.
|
||||
|
||||
// ==========================================
|
||||
@@ -314,7 +334,7 @@
|
||||
"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.
|
||||
"vacuumIntervalDays": 7 // Minimum days between VACUUM runs.
|
||||
} // Retention setting.
|
||||
} // Enable/disable immersion tracking.
|
||||
}
|
||||
|
||||
@@ -95,4 +95,6 @@ Read first. Keep concise.
|
||||
| `codex-docs-video-thumb-cache-20260223T033929Z-k8p2` | `codex-docs-video-thumb-cache` | `Fix docs landing page demo video thumbnail staleness after direct asset replacement.` | `handoff` | `docs/subagents/agents/codex-docs-video-thumb-cache-20260223T033929Z-k8p2.md` | `2026-02-23T03:44:04Z` |
|
||||
| `codex-development-docs-review-20260223T034520Z-2ebb` | `codex-development-docs-review` | `Review codebase and refresh docs/development.md to match current project state.` | `done` | `docs/subagents/agents/codex-development-docs-review-20260223T034520Z-2ebb.md` | `2026-02-23T03:49:16Z` |
|
||||
| `opencode-bun-migration-20260223T043000Z-k9m2` | `opencode-bun-migration` | `Execute TASK-115 Bun-only migration plan: parity map, dist/utility script migration, CI/docs cutover.` | `handoff` | `docs/subagents/agents/opencode-bun-migration-20260223T043000Z-k9m2.md` | `2026-02-23T04:36:00Z` |
|
||||
| `opencode-initial-release-plan-20260223T044059Z-p7k2` | `opencode-initial-release-plan` | `Analyze main history and draft copy/paste initial-release history-cleanup plan.` | `planning` | `docs/subagents/agents/opencode-initial-release-plan-20260223T044059Z-p7k2.md` | `2026-02-23T04:40:59Z` |
|
||||
| `opencode-initial-release-plan-20260223T044059Z-p7k2` | `opencode-initial-release-plan` | `Analyze main history and draft copy/paste initial-release history-cleanup plan.` | `handoff` | `docs/subagents/agents/opencode-initial-release-plan-20260223T044059Z-p7k2.md` | `2026-02-23T04:47:30Z` |
|
||||
| `codex-plugin-start-overlay-gate-20260223T044347Z-k3n8` | `codex-plugin-start-overlay-gate` | `Fix mpv plugin regression where start is refused if SubMiner process is not already running (TASK-117).` | `handoff` | `docs/subagents/agents/codex-plugin-start-overlay-gate-20260223T044347Z-k3n8.md` | `2026-02-23T04:47:40Z` |
|
||||
| `codex-commit-changes-20260223T050731Z-rer4` | `codex-commit-changes` | `Commit current working tree changes with a conventional commit message derived from content.` | `in_progress` | `docs/subagents/agents/codex-commit-changes-20260223T050731Z-rer4.md` | `2026-02-23T05:07:31Z` |
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# Agent: `codex-commit-changes-20260223T050731Z-rer4`
|
||||
|
||||
- alias: `codex-commit-changes`
|
||||
- mission: `Commit current working tree changes requested by user with a content-derived conventional commit message.`
|
||||
- status: `in_progress`
|
||||
- branch: `main`
|
||||
- started_at: `2026-02-23T05:07:31Z`
|
||||
- heartbeat_minutes: `5`
|
||||
|
||||
## Current Work (newest first)
|
||||
- [2026-02-23T05:07:31Z] intent: inspect diff scope, preserve existing user/agent edits, stage all current changes, and commit once with conventional format.
|
||||
|
||||
## Files Touched
|
||||
- `docs/subagents/INDEX.md`
|
||||
- `docs/subagents/collaboration.md`
|
||||
- `docs/subagents/agents/codex-commit-changes-20260223T050731Z-rer4.md`
|
||||
|
||||
## Assumptions
|
||||
- User request "commit changes" means commit all current working-tree changes.
|
||||
- No additional code edits are required beyond required coordination bookkeeping.
|
||||
|
||||
## Open Questions / Blockers
|
||||
- None.
|
||||
|
||||
## Next Step
|
||||
- Stage all changes and create one conventional commit message based on the diff.
|
||||
@@ -0,0 +1,30 @@
|
||||
# Agent: `codex-plugin-start-overlay-gate-20260223T044347Z-k3n8`
|
||||
|
||||
- alias: `codex-plugin-start-overlay-gate`
|
||||
- mission: `Fix mpv plugin regression where start is refused if SubMiner process is not already running (TASK-117).`
|
||||
- status: `handoff`
|
||||
- branch: `main`
|
||||
- started_at: `2026-02-23T04:49:00Z`
|
||||
- heartbeat_minutes: `5`
|
||||
|
||||
## Current Work (newest first)
|
||||
- [2026-02-23T04:47:40Z] completed: patched cold-start gate in `plugin/subminer.lua`, added Lua regression harness, validated red->green + syntax + focused launcher mpv tests.
|
||||
- [2026-02-23T04:49:00Z] intent: reproduce root cause from user logs; add red test first; patch plugin start gate with minimal behavior change.
|
||||
|
||||
## Files Touched
|
||||
- `plugin/subminer.lua`
|
||||
- `scripts/test-plugin-start-gate.lua`
|
||||
- `backlog/tasks/task-117 - Fix-mpv-plugin-overlay-start-gate-when-SubMiner-is-not-running.md`
|
||||
- `docs/subagents/INDEX.md`
|
||||
- `docs/subagents/collaboration.md`
|
||||
- `docs/subagents/agents/codex-plugin-start-overlay-gate-20260223T044347Z-k3n8.md`
|
||||
|
||||
## Assumptions
|
||||
- Regression introduced by `is_subminer_ipc_ready()` check at start of `start_overlay()`.
|
||||
- Cold-start should not require pre-existing process; socket checks remain valid once process exists.
|
||||
|
||||
## Open Questions / Blockers
|
||||
- None.
|
||||
|
||||
## Next Step
|
||||
- User runtime verification: launch with `SUBMINER_APPIMAGE_PATH=... ./dist/launcher/subminer --log-level debug ...` and trigger `y-s`.
|
||||
@@ -0,0 +1,35 @@
|
||||
# Agent: `opencode-initial-release-plan-20260223T044059Z-p7k2`
|
||||
|
||||
- alias: `opencode-initial-release-plan`
|
||||
- mission: `Analyze main history and draft copy/paste initial-release history-cleanup plan.`
|
||||
- status: `handoff`
|
||||
- branch: `main`
|
||||
- started_at: `2026-02-23T04:40:59Z`
|
||||
- heartbeat_minutes: `5`
|
||||
|
||||
## Current Work (newest first)
|
||||
|
||||
- [2026-02-23T04:47:30Z] handoff: added `initial-release.md` with orphan-branch + curated-commit cutover commands, backup refs, validation checks, force-with-lease push, and team resync instructions.
|
||||
- [2026-02-23T04:44:30Z] progress: analyzed `main` history (325 commits; high refactor/noise skew) and confirmed release-shaped repo layout for snapshot regrouping.
|
||||
- [2026-02-23T04:40:59Z] intent: inspect commit history and current repository state, then write `initial-release.md` with exact cutover commands.
|
||||
|
||||
## Files Touched
|
||||
|
||||
- `docs/subagents/agents/opencode-initial-release-plan-20260223T044059Z-p7k2.md`
|
||||
- `docs/subagents/INDEX.md`
|
||||
- `docs/subagents/collaboration.md`
|
||||
- `initial-release.md`
|
||||
- `backlog/tasks/task-116 - Analyze-git-history-and-draft-initial-release-cleanup-plan.md`
|
||||
|
||||
## Assumptions
|
||||
|
||||
- history rewrite is allowed because repo is private and pre-release.
|
||||
- recommendation should prefer low-risk deterministic workflow over complex interactive rebase.
|
||||
|
||||
## Open Questions / Blockers
|
||||
|
||||
- none.
|
||||
|
||||
## Next Step
|
||||
|
||||
- share recommendation + path to `initial-release.md`; execute cutover during freeze window.
|
||||
@@ -148,6 +148,8 @@ Shared notes. Append-only.
|
||||
|
||||
## 2026-02-23
|
||||
|
||||
- [2026-02-23T04:49:00Z] [codex-plugin-start-overlay-gate-20260223T044347Z-k3n8|codex-plugin-start-overlay-gate] overlap note: touching `plugin/subminer.lua` start gate path for cold-start regression (`Refusing to start overlay: SubMiner process not running`); adding TASK-117 + focused regression harness.
|
||||
- [2026-02-23T04:47:40Z] [codex-plugin-start-overlay-gate-20260223T044347Z-k3n8|codex-plugin-start-overlay-gate] completed TASK-117: `start_overlay()` now allows cold-start when process absent; added `scripts/test-plugin-start-gate.lua` red/green regression harness; verified with `luac -p plugin/subminer.lua` and `bun test launcher/mpv.test.ts`.
|
||||
- [2026-02-23T01:10:27Z] [opencode-task109-discord-presence-20260223T011027Z-j9r4|opencode-task109-discord-presence] starting TASK-109 closure pass via Backlog MCP + writing-plans/executing-plans; scope validate existing Discord config/runtime/docs changes, close remaining DoD evidence, and finalize task status if gates pass.
|
||||
- [2026-02-23T01:15:39Z] [opencode-task109-discord-presence-20260223T011027Z-j9r4|opencode-task109-discord-presence] user feedback from real Discord session: status resumed to Playing with noticeable delay; tuned default `discordPresence.updateIntervalMs` from 15000 to 3000 in defaults/docs/examples and updated focused config expectations; reran focused config + discord presence tests green.
|
||||
- [2026-02-23T01:27:55Z] [codex-task88-yomitan-flow-20260223T012755Z-x4m2|codex-task88-yomitan-flow] starting TASK-88 via Backlog MCP + writing-plans/executing-plans; expected overlap in tokenizer modules (`src/core/services/tokenizer*`, Yomitan flow wiring/tests); will keep scope to MeCab fallback removal and token flow simplification.
|
||||
@@ -169,3 +171,5 @@ Shared notes. Append-only.
|
||||
- [2026-02-23T04:30:00Z] [opencode-bun-migration-20260223T043000Z-k9m2|opencode-bun-migration] starting TASK-115 Bun-only migration execution; initial scope `package.json`, CI/release workflows, and setup docs to remove Node requirements after parity checks.
|
||||
- [2026-02-23T04:36:00Z] [opencode-bun-migration-20260223T043000Z-k9m2|opencode-bun-migration] completed TASK-115 Bun-only migration pass: dist/utility commands moved off direct Node invocation, CI/release Node setup removed, Bun parity matrix + docs updates landed, full Bun validation gate suite passed, and TASK-115 + child tasks finalized Done in Backlog.
|
||||
- [2026-02-23T04:40:59Z] [opencode-initial-release-plan-20260223T044059Z-p7k2|opencode-initial-release-plan] starting user-requested release-history cleanup planning pass; scope git-history analysis + current-state review + `initial-release.md` command playbook.
|
||||
- [2026-02-23T04:47:30Z] [opencode-initial-release-plan-20260223T044059Z-p7k2|opencode-initial-release-plan] completed planning pass: analyzed commit history/current state and added `initial-release.md` with recommended orphan-history + 7-commit split plan, validation, cutover, and teammate resync commands.
|
||||
- [2026-02-23T05:07:31Z] [codex-commit-changes-20260223T050731Z-rer4|codex-commit-changes] starting user-requested commit pass for current working tree; scope is stage all existing tracked/untracked changes and create conventional commit without additional behavior edits.
|
||||
|
||||
167
initial-release.md
Normal file
167
initial-release.md
Normal file
@@ -0,0 +1,167 @@
|
||||
# Initial Release Git History Cleanup Plan
|
||||
|
||||
## Recommendation
|
||||
|
||||
Given the current `main` history shape (325 commits, heavy refactor churn, and many internal maintenance commits), the best pre-release cleanup path is:
|
||||
|
||||
1. Preserve the old `main` history in remote backup refs.
|
||||
2. Build a **new orphan history** from the current project snapshot.
|
||||
3. Recreate history as **7 curated commits** grouped by release-facing domains.
|
||||
4. Verify tree equality with old `main` and replace `origin/main` once.
|
||||
|
||||
This avoids fragile `rebase -i --root` over 300+ commits while still keeping meaningful archaeology for future maintenance.
|
||||
|
||||
## Why this is best for this repo
|
||||
|
||||
- Current history is dominated by refactor/noise commits (`refactor`: 138, `other`: 91).
|
||||
- Commit velocity is very high and bursty (multiple 30-45 commit days), so interactive cleanup is risky/time-expensive.
|
||||
- Codebase is already mature and release-shaped (Electron app + launcher + plugin + docs + CI), so snapshot regrouping is cleaner.
|
||||
- You are pre-release and private; a one-time force update is low risk if coordinated.
|
||||
|
||||
---
|
||||
|
||||
## Pre-cutover checklist
|
||||
|
||||
Run from repository root.
|
||||
|
||||
```bash
|
||||
git checkout main
|
||||
git pull --ff-only origin main
|
||||
```
|
||||
|
||||
Coordinate freeze:
|
||||
|
||||
- Pause merges/pushes to `main`.
|
||||
- Ask teammates to stop rebasing onto `main` during cutover.
|
||||
|
||||
---
|
||||
|
||||
## Copy/paste command sequence
|
||||
|
||||
### 1) Safety backups (local + remote)
|
||||
|
||||
```bash
|
||||
set -euo pipefail
|
||||
|
||||
git checkout main
|
||||
git pull --ff-only origin main
|
||||
|
||||
OLD_MAIN_COMMIT="$(git rev-parse main)"
|
||||
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
|
||||
|
||||
echo "Old main commit: ${OLD_MAIN_COMMIT}"
|
||||
echo "Cutover stamp: ${STAMP}"
|
||||
|
||||
git tag "pre-release-history-backup-${STAMP}" "${OLD_MAIN_COMMIT}"
|
||||
git branch "archive/pre-release-history-${STAMP}" "${OLD_MAIN_COMMIT}"
|
||||
|
||||
git push origin "pre-release-history-backup-${STAMP}"
|
||||
git push origin "archive/pre-release-history-${STAMP}"
|
||||
```
|
||||
|
||||
### 2) Handle dirty workspace safely
|
||||
|
||||
If `git status --short` is not empty:
|
||||
|
||||
```bash
|
||||
git stash push -u -m "initial-release-history-cleanup-${STAMP}"
|
||||
```
|
||||
|
||||
### 3) Build new clean history on orphan branch
|
||||
|
||||
```bash
|
||||
git checkout --orphan release/main-clean
|
||||
git reset
|
||||
```
|
||||
|
||||
Now create curated commits.
|
||||
|
||||
```bash
|
||||
# Commit 1: repository scaffolding and build toolchain
|
||||
git add .gitignore .gitmodules .npmrc .prettierignore .prettierrc.json AGENTS.md LICENSE Makefile package.json bun.lock tsconfig.json tsconfig.renderer.json build scripts .github
|
||||
git commit -m "chore: bootstrap repository tooling and release automation"
|
||||
|
||||
# Commit 2: core desktop app runtime
|
||||
git add src
|
||||
git commit -m "feat(core): add Electron runtime, services, and app composition"
|
||||
|
||||
# Commit 3: launcher and mpv plugin integration
|
||||
git add launcher plugin subminer
|
||||
git commit -m "feat(integration): add launcher and mpv plugin workflows"
|
||||
|
||||
# Commit 4: bundled assets and vendor payloads
|
||||
git add assets vendor
|
||||
git commit -m "feat(assets): bundle runtime assets and vendor dependencies"
|
||||
|
||||
# Commit 5: test suites and verification harnesses
|
||||
git add tests
|
||||
git commit -m "test: add regression suites and harness coverage"
|
||||
|
||||
# Commit 6: documentation and examples
|
||||
git add README.md docs config.example.jsonc
|
||||
git commit -m "docs: add setup guides, architecture docs, and config examples"
|
||||
|
||||
# Commit 7: project/backlog metadata and remaining files (if any)
|
||||
git add -A
|
||||
if ! git diff --cached --quiet; then
|
||||
git commit -m "chore: add project management metadata and remaining repository files"
|
||||
fi
|
||||
```
|
||||
|
||||
### 4) Validate tree equivalence to old main snapshot
|
||||
|
||||
```bash
|
||||
git diff --stat "${OLD_MAIN_COMMIT}^{tree}" HEAD^{tree}
|
||||
git diff --name-status "${OLD_MAIN_COMMIT}^{tree}" HEAD^{tree}
|
||||
```
|
||||
|
||||
Expected: no output. If output exists, stop and inspect before pushing.
|
||||
|
||||
### 5) Replace `origin/main`
|
||||
|
||||
```bash
|
||||
git push --force-with-lease origin release/main-clean:main
|
||||
```
|
||||
|
||||
### 6) Tag cleaned initial-release baseline
|
||||
|
||||
```bash
|
||||
git fetch origin
|
||||
git checkout main
|
||||
git reset --hard origin/main
|
||||
git tag "v0.1.0-initial-clean-history"
|
||||
git push origin "v0.1.0-initial-clean-history"
|
||||
```
|
||||
|
||||
### 7) Restore local stashed work (if stashed earlier)
|
||||
|
||||
```bash
|
||||
git stash list
|
||||
git stash pop
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Team resync message (send after cutover)
|
||||
|
||||
```text
|
||||
main history was rewritten for initial release cleanup.
|
||||
|
||||
If you have no local work:
|
||||
git fetch origin
|
||||
git checkout main
|
||||
git reset --hard origin/main
|
||||
|
||||
If you have local work:
|
||||
git fetch origin
|
||||
git checkout -b backup/<name>-before-main-rewrite
|
||||
# then rebase/cherry-pick your feature commits onto new origin/main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
- This plan intentionally avoids `git rebase -i --root` to reduce conflict risk.
|
||||
- `--force-with-lease` is mandatory safety rail.
|
||||
- Keep the backup tag/branch until after public release stabilization.
|
||||
@@ -1526,7 +1526,8 @@ end
|
||||
|
||||
local function start_overlay(overrides)
|
||||
local socket_ready, reason = is_subminer_ipc_ready()
|
||||
if not socket_ready then
|
||||
local process_not_running = reason == "SubMiner process not running"
|
||||
if not socket_ready and not process_not_running then
|
||||
subminer_log("warn", "process", "Refusing to start overlay: " .. tostring(reason))
|
||||
show_osd("SubMiner IPC not set up. Launch mpv with --input-ipc-server=/tmp/subminer-socket")
|
||||
return
|
||||
|
||||
193
scripts/test-plugin-start-gate.lua
Normal file
193
scripts/test-plugin-start-gate.lua
Normal file
@@ -0,0 +1,193 @@
|
||||
local function run_plugin_scenario(config)
|
||||
config = config or {}
|
||||
|
||||
local recorded = {
|
||||
async_calls = {},
|
||||
script_messages = {},
|
||||
osd = {},
|
||||
logs = {},
|
||||
}
|
||||
|
||||
local function make_mp_stub()
|
||||
local mp = {}
|
||||
|
||||
function mp.get_property(name)
|
||||
if name == "platform" then
|
||||
return config.platform or "linux"
|
||||
end
|
||||
if name == "filename/no-ext" then
|
||||
return config.filename_no_ext or ""
|
||||
end
|
||||
if name == "filename" then
|
||||
return config.filename or ""
|
||||
end
|
||||
if name == "path" then
|
||||
return config.path or ""
|
||||
end
|
||||
if name == "media-title" then
|
||||
return config.media_title or ""
|
||||
end
|
||||
return ""
|
||||
end
|
||||
|
||||
function mp.get_property_native(_name)
|
||||
return config.chapter_list or {}
|
||||
end
|
||||
|
||||
function mp.command_native(command)
|
||||
local args = command.args or {}
|
||||
if args[1] == "ps" then
|
||||
return {
|
||||
status = 0,
|
||||
stdout = config.process_list or "",
|
||||
stderr = "",
|
||||
}
|
||||
end
|
||||
if args[1] == "curl" then
|
||||
return { status = 0, stdout = "{}", stderr = "" }
|
||||
end
|
||||
return { status = 0, stdout = "", stderr = "" }
|
||||
end
|
||||
|
||||
function mp.command_native_async(command, callback)
|
||||
recorded.async_calls[#recorded.async_calls + 1] = command
|
||||
if callback then
|
||||
callback(true, { status = 0, stdout = "", stderr = "" }, nil)
|
||||
end
|
||||
end
|
||||
|
||||
function mp.add_timeout(_seconds, callback)
|
||||
if callback then
|
||||
callback()
|
||||
end
|
||||
end
|
||||
|
||||
function mp.register_script_message(name, fn)
|
||||
recorded.script_messages[name] = fn
|
||||
end
|
||||
|
||||
function mp.add_key_binding(_keys, _name, _fn) end
|
||||
function mp.register_event(_name, _fn) end
|
||||
function mp.add_hook(_name, _prio, _fn) end
|
||||
function mp.observe_property(_name, _kind, _fn) end
|
||||
function mp.osd_message(message, _duration)
|
||||
recorded.osd[#recorded.osd + 1] = message
|
||||
end
|
||||
function mp.get_time()
|
||||
return 0
|
||||
end
|
||||
function mp.commandv(...) end
|
||||
function mp.set_property_native(...) end
|
||||
function mp.get_script_name()
|
||||
return "subminer"
|
||||
end
|
||||
|
||||
return mp
|
||||
end
|
||||
|
||||
local mp = make_mp_stub()
|
||||
local options = {}
|
||||
local utils = {}
|
||||
|
||||
function options.read_options(target, _name)
|
||||
if config.socket_path then
|
||||
target.socket_path = config.socket_path
|
||||
end
|
||||
end
|
||||
|
||||
function utils.file_info(path)
|
||||
local exists = config.files and config.files[path]
|
||||
if exists then
|
||||
return { is_dir = false }
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function utils.join_path(...)
|
||||
local parts = { ... }
|
||||
return table.concat(parts, "/")
|
||||
end
|
||||
|
||||
function utils.parse_json(_json)
|
||||
return {}, nil
|
||||
end
|
||||
|
||||
package.loaded["mp"] = nil
|
||||
package.loaded["mp.input"] = nil
|
||||
package.loaded["mp.msg"] = nil
|
||||
package.loaded["mp.options"] = nil
|
||||
package.loaded["mp.utils"] = nil
|
||||
|
||||
package.preload["mp"] = function()
|
||||
return mp
|
||||
end
|
||||
package.preload["mp.input"] = function()
|
||||
return {
|
||||
select = function(_) end,
|
||||
}
|
||||
end
|
||||
package.preload["mp.msg"] = function()
|
||||
return {
|
||||
info = function(line)
|
||||
recorded.logs[#recorded.logs + 1] = line
|
||||
end,
|
||||
warn = function(line)
|
||||
recorded.logs[#recorded.logs + 1] = line
|
||||
end,
|
||||
error = function(line)
|
||||
recorded.logs[#recorded.logs + 1] = line
|
||||
end,
|
||||
debug = function(line)
|
||||
recorded.logs[#recorded.logs + 1] = line
|
||||
end,
|
||||
}
|
||||
end
|
||||
package.preload["mp.options"] = function()
|
||||
return options
|
||||
end
|
||||
package.preload["mp.utils"] = function()
|
||||
return utils
|
||||
end
|
||||
|
||||
local ok, err = pcall(dofile, "plugin/subminer.lua")
|
||||
if not ok then
|
||||
return nil, err, recorded
|
||||
end
|
||||
return recorded, nil, recorded
|
||||
end
|
||||
|
||||
local function assert_true(condition, message)
|
||||
if condition then
|
||||
return
|
||||
end
|
||||
error(message)
|
||||
end
|
||||
|
||||
local function find_start_call(async_calls)
|
||||
for _, call in ipairs(async_calls) do
|
||||
local args = call.args or {}
|
||||
for i = 1, #args do
|
||||
if args[i] == "--start" then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local binary_path = "/tmp/subminer-binary"
|
||||
|
||||
do
|
||||
local recorded, err = run_plugin_scenario({
|
||||
process_list = "",
|
||||
files = {
|
||||
[binary_path] = true,
|
||||
},
|
||||
})
|
||||
assert_true(recorded ~= nil, "plugin failed to load for cold-start scenario: " .. tostring(err))
|
||||
assert_true(recorded.script_messages["subminer-start"] ~= nil, "subminer-start script message not registered")
|
||||
recorded.script_messages["subminer-start"]("texthooker=no")
|
||||
assert_true(find_start_call(recorded.async_calls), "expected cold-start to invoke --start command when process is absent")
|
||||
end
|
||||
|
||||
print("plugin start gate regression tests: OK")
|
||||
Reference in New Issue
Block a user