diff --git a/docs/architecture.md b/docs/architecture.md
index 0f39e58..90084da 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -128,7 +128,7 @@ src/renderer/
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
+flowchart LR
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
@@ -137,94 +137,70 @@ flowchart TD
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 — composition root"]:::entry
-
- subgraph Comp["Composition — src/main/"]
- direction TB
- Startup["Startup & Lifecycle
startup · app-lifecycle · startup-lifecycle · state"]:::comp
- Wiring["Runtime Wiring
ipc-runtime · cli-runtime · overlay-runtime · subsync-runtime"]:::comp
- Composers["Composers
mpv-runtime · anilist-tracking · jellyfin-runtime"]:::comp
- end
-
- subgraph Svc["Services — src/core/services/"]
- direction TB
- subgraph SvcRow1[" "]
- direction LR
- Mpv["MPV Stack
transport · protocol
properties · render-metrics"]:::svc
- Overlay["Overlay Manager
window · geometry
visibility · bridge"]:::svc
- end
- subgraph SvcRow2[" "]
- direction LR
- Mining["Mining & Subtitles
mining · field-grouping
subtitle-ws · tokenizer"]:::svc
- Integrations["Integrations
jimaku · subsync · texthooker
yomitan · discord-presence"]:::svc
- end
- subgraph SvcRow3[" "]
- direction LR
- Tracking["Tracking
anilist · jellyfin-remote
immersion-tracker"]:::svc
- Config["Config & Runtime
config-hot-reload
runtime-options"]:::svc
- end
- end
-
- Bridge(["preload.ts — Electron IPC bridge"]):::bridge
-
- subgraph Rend["Renderer — src/renderer/"]
- direction TB
- subgraph Windows["Three overlay windows"]
- direction LR
- Visible["Visible
interactive Yomitan lookups"]:::rend
- Invisible["Invisible
mpv-matched positioning"]:::rend
- Secondary["Secondary
secondary subtitle bar"]:::rend
- end
- UI["subtitle-render · positioning · handlers · modals"]:::rend
+ subgraph ExtRt["External Runtimes"]
+ Launcher["launcher/
CLI dispatch"]:::extrt
+ Plugin["subminer.lua
mpv plugin"]:::extrt
end
subgraph Ext["External Systems"]
- direction LR
mpvExt["mpv player"]:::ext
AnkiExt["AnkiConnect"]:::ext
JimakuExt["Jimaku API"]:::ext
- TrackerExt["Window Tracker
Hyprland · Sway · X11 · macOS"]:::ext
+ TrackerExt["Window Tracker
Hyprland · Sway
X11 · macOS"]:::ext
AnilistExt["AniList API"]:::ext
JellyfinExt["Jellyfin"]:::ext
DiscordExt["Discord RPC"]:::ext
end
- subgraph ExtRt["External Runtimes"]
- direction LR
- Launcher["launcher/
CLI command dispatch"]:::extrt
- Plugin["subminer.lua
mpv plugin"]:::extrt
+ Main["main.ts
composition root"]:::entry
+
+ subgraph Comp["Composition — src/main/"]
+ Startup["Startup & Lifecycle
startup · app-lifecycle
startup-lifecycle · state"]:::comp
+ Wiring["Runtime Wiring
ipc-runtime · cli-runtime
overlay-runtime"]:::comp
+ Composers["Composers
mpv · anilist
jellyfin"]:::comp
end
- Main -->|"delegates"| Comp
- Startup -->|"initializes"| Svc
- Wiring -->|"dispatches to"| Svc
- Composers -->|"wires"| Svc
+ subgraph Svc["Services — src/core/services/"]
+ Mpv["MPV Stack
transport · protocol
properties · metrics"]:::svc
+ Overlay["Overlay Manager
window · geometry
visibility · bridge"]:::svc
+ Mining["Mining & Subtitles
mining · field-grouping
subtitle-ws · tokenizer"]:::svc
+ Integrations["Integrations
jimaku · subsync
texthooker · yomitan"]:::svc
+ Tracking["Tracking
anilist · jellyfin
immersion · discord"]:::svc
+ Config["Config & Runtime
hot-reload
runtime-options"]:::svc
+ end
- Overlay <-->Bridge
- Mining <--> Bridge
- Bridge <--> Visible
- Bridge <--> Invisible
- Bridge <--> Secondary
- Windows --> UI
+ Bridge(["preload.ts
Electron IPC"]):::bridge
- Mpv <-->|"JSON IPC socket"| mpvExt
- Mining -->|"HTTP"| AnkiExt
- Integrations -->|"HTTP"| JimakuExt
- Overlay -->|"platform API"| TrackerExt
- Tracking -->|"HTTP"| AnilistExt
- Tracking -->|"HTTP"| JellyfinExt
- Integrations -->|"RPC"| DiscordExt
+ subgraph Rend["Renderer — src/renderer/"]
+ Visible["Visible window
Yomitan lookups"]:::rend
+ Invisible["Invisible window
mpv positioning"]:::rend
+ Secondary["Secondary window
subtitle bar"]:::rend
+ UI["subtitle-render
positioning
handlers · modals"]:::rend
+ end
- Launcher -->|"CLI passthrough"| Main
- Plugin -->|"IPC socket"| mpvExt
+ Launcher -->|"CLI"| Main
+ Plugin -->|"IPC"| mpvExt
+
+ Main --> Comp
+ Comp --> Svc
+
+ mpvExt <-->|"JSON socket"| Mpv
+ AnkiExt <-->|"HTTP"| Mining
+ JimakuExt <-->|"HTTP"| Integrations
+ TrackerExt <-->|"platform"| Overlay
+ AnilistExt <-->|"HTTP"| Tracking
+ JellyfinExt <-->|"HTTP"| Tracking
+ DiscordExt <-->|"RPC"| Integrations
+
+ Overlay & Mining --> Bridge
+ Bridge --> Visible
+ Bridge --> Invisible
+ Bridge --> Secondary
+ Visible & Invisible & Secondary --> UI
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
```
diff --git a/flow-diagram-fullview.png b/flow-diagram-fullview.png
new file mode 100644
index 0000000..6b608df
Binary files /dev/null and b/flow-diagram-fullview.png differ
diff --git a/flow-review-full.png b/flow-review-full.png
new file mode 100644
index 0000000..70c2623
Binary files /dev/null and b/flow-review-full.png differ
diff --git a/lifecycle-diagram-fullview.png b/lifecycle-diagram-fullview.png
new file mode 100644
index 0000000..55c21a9
Binary files /dev/null and b/lifecycle-diagram-fullview.png differ
diff --git a/plugin/subminer.lua b/plugin/subminer.lua
index 2955518..d1a8649 100644
--- a/plugin/subminer.lua
+++ b/plugin/subminer.lua
@@ -1595,10 +1595,7 @@ local function start_overlay_from_script_message(...)
end
local function stop_overlay()
- if not is_subminer_app_running() then
- return
- end
- if not state.binary_available then
+ if not ensure_binary_available() then
subminer_log("error", "binary", "SubMiner binary not found")
show_osd("Error: binary not found")
return
@@ -1626,10 +1623,7 @@ local function stop_overlay()
end
local function toggle_overlay()
- if not is_subminer_app_running() then
- return
- end
- if not state.binary_available then
+ if not ensure_binary_available() then
subminer_log("error", "binary", "SubMiner binary not found")
show_osd("Error: binary not found")
return
@@ -1653,10 +1647,7 @@ local function toggle_overlay()
end
local function toggle_invisible_overlay()
- if not is_subminer_app_running() then
- return
- end
- if not state.binary_available then
+ if not ensure_binary_available() then
subminer_log("error", "binary", "SubMiner binary not found")
show_osd("Error: binary not found")
return
@@ -1684,10 +1675,7 @@ local function toggle_invisible_overlay()
end
local function show_invisible_overlay()
- if not is_subminer_app_running() then
- return
- end
- if not state.binary_available then
+ if not ensure_binary_available() then
subminer_log("error", "binary", "SubMiner binary not found")
show_osd("Error: binary not found")
return
@@ -1715,10 +1703,7 @@ local function show_invisible_overlay()
end
local function hide_invisible_overlay()
- if not is_subminer_app_running() then
- return
- end
- if not state.binary_available then
+ if not ensure_binary_available() then
subminer_log("error", "binary", "SubMiner binary not found")
show_osd("Error: binary not found")
return
@@ -1811,10 +1796,7 @@ local function show_menu()
end
restart_overlay = function()
- if not is_subminer_app_running() then
- return
- end
- if not state.binary_available then
+ if not ensure_binary_available() then
subminer_log("error", "binary", "SubMiner binary not found")
show_osd("Error: binary not found")
return
diff --git a/src/main.ts b/src/main.ts
index 39a5e10..dbe3ef8 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -2926,11 +2926,30 @@ function handleMineSentenceDigit(count: number): void {
handleMineSentenceDigitHandler(count);
}
+function ensureOverlayWindowsReadyForVisibilityActions(): void {
+ if (!appState.overlayRuntimeInitialized) {
+ initializeOverlayRuntime();
+ return;
+ }
+
+ const mainWindow = overlayManager.getMainWindow();
+ if (!mainWindow || mainWindow.isDestroyed()) {
+ createMainWindow();
+ }
+
+ const invisibleWindow = overlayManager.getInvisibleWindow();
+ if (!invisibleWindow || invisibleWindow.isDestroyed()) {
+ createInvisibleWindow();
+ }
+}
+
function setVisibleOverlayVisible(visible: boolean): void {
+ ensureOverlayWindowsReadyForVisibilityActions();
setVisibleOverlayVisibleHandler(visible);
}
function setInvisibleOverlayVisible(visible: boolean): void {
+ ensureOverlayWindowsReadyForVisibilityActions();
setInvisibleOverlayVisibleHandler(visible);
if (visible) {
subtitleProcessingController.refreshCurrentSubtitle(appState.currentSubText);
@@ -2938,9 +2957,11 @@ function setInvisibleOverlayVisible(visible: boolean): void {
}
function toggleVisibleOverlay(): void {
+ ensureOverlayWindowsReadyForVisibilityActions();
toggleVisibleOverlayHandler();
}
function toggleInvisibleOverlay(): void {
+ ensureOverlayWindowsReadyForVisibilityActions();
toggleInvisibleOverlayHandler();
}
function setOverlayVisible(visible: boolean): void {