diff --git a/changes/docs-startup-flow-config-options.md b/changes/docs-startup-flow-config-options.md
new file mode 100644
index 00000000..b22ddaa3
--- /dev/null
+++ b/changes/docs-startup-flow-config-options.md
@@ -0,0 +1,7 @@
+type: changed
+area: docs
+
+- Documented all config options that were present in `config.example.jsonc` but missing from the configuration reference: `subtitleStyle.primaryDefaultMode`, `stats.markWatchedKey`, `immersionTracking.lifetimeSummaries.*`, and all seven `mpv.*` launcher options (`socketPath`, `backend`, `autoStartSubMiner`, `pauseUntilOverlayReady`, `subminerBinaryPath`, `aniskipEnabled`, `aniskipButtonKey`).
+- Added a **Playback Startup Flow** diagram to the Architecture page showing how the managed launch (`subminer` CLI, app, Windows shortcut) injects the plugin, establishes the IPC socket, and brings up the overlay via the two convergent triggers.
+- Added a **Runtime Sockets** section and diagram to the IPC + Runtime Contracts page showing the mpv IPC socket and app control socket topology.
+- Added cross-reference pointers in the MPV Plugin and Troubleshooting pages.
diff --git a/docs-site/architecture.md b/docs-site/architecture.md
index 8509770d..b4671cd4 100644
--- a/docs-site/architecture.md
+++ b/docs-site/architecture.md
@@ -269,6 +269,43 @@ For domains migrated to reducer-style transitions (for example AniList token/que
- Reducer boundary: when a domain has transition helpers in `src/main/state.ts`, new callsites should route updates through those helpers instead of ad-hoc object mutation in `main.ts` or composers.
- Tests for migrated domains should assert both the intended field changes and non-targeted field invariants.
+## Playback Startup Flow
+
+Before the app boots, something has to launch mpv, inject the plugin, and bring the overlay up. SubMiner-managed launches own this step — the `subminer` launcher, the app's own playback, and the packaged Windows shortcut all follow the same path. The launcher reads `config.jsonc`, spawns mpv with the IPC socket and the bundled plugin, and passes runtime settings as `--script-opts`. The plugin never reads a config file: the shipped `subminer.conf` is intentionally empty so command-line opts always win.
+
+Once mpv is up, exactly one of two triggers brings up the overlay. On a first launch the plugin's `file-loaded` hook self-starts the app once the socket is ready (because the launcher injected `auto_start=yes`). When the app is already running — or for explicit `--start-overlay` and YouTube flows — the launcher instead attaches over the control socket and suppresses the plugin's auto-start, so the two never fire together. Both converge on the same app bring-up, which then runs the Program Lifecycle below.
+
+```mermaid
+flowchart TB
+ classDef entry fill:#c6a0f6,stroke:#494d64,color:#24273a,stroke-width:2px,font-weight:bold
+ classDef extrt fill:#eed49f,stroke:#494d64,color:#24273a,stroke-width:1.5px
+ classDef decision fill:#f5a97f,stroke:#494d64,color:#24273a,stroke-width:1.5px
+ classDef proc fill:#8aadf4,stroke:#494d64,color:#24273a,stroke-width:1.5px
+ classDef app fill:#b7bdf8,stroke:#494d64,color:#24273a,stroke-width:1.5px
+ classDef overlay fill:#8bd5ca,stroke:#494d64,color:#24273a,stroke-width:1.5px
+
+ Entry["Managed launch
subminer CLI · app · Windows shortcut"]:::entry
+ Entry --> Cfg["Launcher reads config.jsonc
→ plugin runtime config"]:::extrt
+ Cfg --> Spawn["Spawn mpv
--input-ipc-server=/tmp/subminer-socket
--script=plugin/subminer/main.lua
--script-opts=subminer-… (auto_start, backend, …)"]:::proc
+ Spawn --> Boot["Plugin boot · read_options('subminer')
empty subminer.conf; CLI opts win"]:::extrt
+ Boot --> Sock["mpv IPC socket ready"]:::proc
+ Sock --> Who{"Overlay trigger"}:::decision
+
+ Who -->|"app already running, or
--start-overlay / YouTube"| Attach["Launcher startOverlay()
attach via control socket
plugin auto-start suppressed"]:::proc
+ Who -->|"first launch, auto_start=yes"| Self["Plugin file-loaded hook
polls socket → process.start_overlay()"]:::extrt
+
+ Attach --> AppUp
+ Self --> AppUp
+
+ AppUp["Spawn / attach SubMiner app
--start --managed-playback --socket … --backend …"]:::app
+ AppUp --> Ctrl["App control server up
/tmp/subminer-control-* dedupes a 2nd launch"]:::app
+ Ctrl --> Life["app.whenReady → Program Lifecycle (below)"]:::app
+ Life --> Conn["MpvIpcClient connects to /tmp/subminer-socket"]:::overlay
+ Conn --> Show["Transparent overlay over mpv
Yomitan lookup · mine"]:::overlay
+```
+
+The runtime sockets in this flow are detailed in [IPC + Runtime Contracts](./ipc-contracts#runtime-sockets).
+
## Program Lifecycle
- **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.
diff --git a/docs-site/configuration.md b/docs-site/configuration.md
index ba5fa25c..f88d091f 100644
--- a/docs-site/configuration.md
+++ b/docs-site/configuration.md
@@ -360,6 +360,7 @@ See `config.example.jsonc` for detailed configuration options.
| Option | Values | Description |
| ---------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------ |
+| `primaryDefaultMode` | string | Default primary subtitle bar visibility mode: `"hidden"`, `"visible"`, or `"hover"` (default: `"visible"`) |
| `subtitleStyle.css` | object | CSS declaration object applied to primary subtitles after normal style defaults. Use CSS property names such as `font-size`. |
| `secondary.css` | object | CSS declaration object applied to secondary subtitles after normal secondary style defaults. |
| `enableJlpt` | boolean | Enable JLPT level underline styling (`false` by default) |
@@ -1377,6 +1378,9 @@ Enable or disable local immersion analytics stored in SQLite for mined subtitles
| `retention.dailyRollupsDays` | integer (`0`-`36500`) | Daily rollup retention window. Default `0` (keep all). |
| `retention.monthlyRollupsDays` | integer (`0`-`36500`) | Monthly rollup retention window. Default `0` (keep all). |
| `retention.vacuumIntervalDays` | integer (`0`-`3650`) | Minimum spacing between `VACUUM` passes. `0` disables vacuum. Default `0` (disabled). |
+| `lifetimeSummaries.global` | `true`, `false` | Maintain global lifetime stats rows (default: `true`). |
+| `lifetimeSummaries.anime` | `true`, `false` | Maintain per-anime lifetime stats rows (default: `true`). |
+| `lifetimeSummaries.media` | `true`, `false` | Maintain per-media lifetime stats rows (default: `true`). |
You can also disable immersion tracking for a single session using:
@@ -1406,6 +1410,7 @@ Configure the local stats UI served from SubMiner and the in-app stats overlay t
{
"stats": {
"toggleKey": "Backquote",
+ "markWatchedKey": "KeyW",
"serverPort": 6969,
"autoStartServer": true,
"autoOpenBrowser": false
@@ -1416,6 +1421,7 @@ Configure the local stats UI served from SubMiner and the in-app stats overlay t
| Option | Values | Description |
| ----------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------- |
| `stats.toggleKey` | Electron key code | Overlay-local key code used to toggle the stats overlay. Default `Backquote`. |
+| `markWatchedKey` | Electron key code | Key code to mark the current video as watched and advance to the next playlist entry. Default `KeyW`. |
| `serverPort` | integer | Localhost port for the browser stats UI. Default `6969`. |
| `autoStartServer` | `true`, `false` | Start the local stats HTTP server automatically once immersion tracking is active. Default `true`. |
| `autoOpenBrowser` | `true`, `false` | When `subminer stats` starts the server on demand, also open the dashboard in your default browser. Default `false`. |
@@ -1435,17 +1441,31 @@ Configure the mpv executable, profile, and window state for SubMiner-managed mpv
{
"mpv": {
"executablePath": "",
+ "launchMode": "normal",
"profile": "",
- "launchMode": "normal"
+ "socketPath": "\\\\.\\pipe\\subminer-socket",
+ "backend": "auto",
+ "autoStartSubMiner": true,
+ "pauseUntilOverlayReady": true,
+ "subminerBinaryPath": "",
+ "aniskipEnabled": true,
+ "aniskipButtonKey": "TAB"
}
}
```
-| Option | Values | Description |
-| ---------------- | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
-| `executablePath` | string | Absolute path to `mpv.exe` for Windows launch flows. Leave empty to auto-discover from `SUBMINER_MPV_PATH` or `PATH` (default `""`) |
-| `profile` | string | mpv profile name passed as `--profile=`. Leave empty to pass no profile (default `""`) |
-| `launchMode` | `"normal"` \| `"maximized"` \| `"fullscreen"` | Window state when SubMiner spawns mpv (default `"normal"`) |
+| Option | Values | Description |
+| ----------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
+| `executablePath` | string | Absolute path to `mpv.exe` for Windows launch flows. Leave empty to auto-discover from `SUBMINER_MPV_PATH` or `PATH` (default `""`) |
+| `profile` | string | mpv profile name passed as `--profile=`. Leave empty to pass no profile (default `""`) |
+| `launchMode` | `"normal"` \| `"maximized"` \| `"fullscreen"` | Window state when SubMiner spawns mpv (default `"normal"`) |
+| `socketPath` | string | mpv IPC socket path used by SubMiner-managed playback and the bundled mpv plugin (default: `\\\\.\\pipe\\subminer-socket`) |
+| `backend` | `"auto"` \| `"hyprland"` \| `"sway"` \| `"x11"` \| `"macos"` \| `"windows"` | Window tracking backend passed to the bundled mpv plugin. Auto detects the current platform (default: `"auto"`) |
+| `autoStartSubMiner` | `true`, `false` | Start SubMiner in the background when SubMiner-managed mpv loads a file (default: `true`) |
+| `pauseUntilOverlayReady`| `true`, `false` | Pause mpv on visible-overlay auto-start until SubMiner signals subtitle tokenization readiness (default: `true`) |
+| `subminerBinaryPath` | string | SubMiner app binary path passed to the bundled mpv plugin. Leave empty to use the launcher-detected app path (default: `""`) |
+| `aniskipEnabled` | `true`, `false` | Enable AniSkip intro detection and skip markers in the bundled mpv plugin (default: `true`) |
+| `aniskipButtonKey` | string | mpv key used to trigger the AniSkip button while the skip marker is visible (default: `"TAB"`) |
If `mpv.profile` is configured and the launcher also receives `--profile`, SubMiner passes both as a comma-separated mpv profile list.
diff --git a/docs-site/ipc-contracts.md b/docs-site/ipc-contracts.md
index 28039bf3..4bf45352 100644
--- a/docs-site/ipc-contracts.md
+++ b/docs-site/ipc-contracts.md
@@ -36,6 +36,37 @@ flowchart TB
style E fill:#ed8796,stroke:#494d64,color:#24273a,stroke-width:1.5px
```
+## Runtime Sockets
+
+The renderer↔main bridge above lives *inside* the Electron app. A separate set of OS sockets connects the app to the other runtimes — mpv and the launcher/plugin. These carry no renderer payloads and bypass the contract/validator layer; they are command and property channels between processes.
+
+- **mpv IPC socket** (`/tmp/subminer-socket`, or `\\.\pipe\subminer-socket` on Windows): the `MpvIpcClient` in the main process connects here to send JSON commands and subscribe to playback/subtitle properties via `observe_property`. Created by mpv's `--input-ipc-server`.
+- **App control socket** (`/tmp/subminer-control--.sock`, or a named pipe on Windows): the launcher and the mpv plugin send CLI-style commands (`--start`, `--show-visible-overlay`, `--texthooker`) to a running app here. It also dedupes a second `subminer` invocation into the existing instance instead of launching twice.
+
+```mermaid
+flowchart LR
+ classDef extrt fill:#eed49f,stroke:#494d64,color:#24273a,stroke-width:1.5px
+ classDef app fill:#b7bdf8,stroke:#494d64,color:#24273a,stroke-width:1.5px
+ classDef ext fill:#a6da95,stroke:#494d64,color:#24273a,stroke-width:1.5px
+
+ subgraph MpvProc["mpv process"]
+ direction TB
+ Mpv["mpv core"]:::ext
+ Plugin["SubMiner plugin (Lua)"]:::extrt
+ end
+
+ Launcher["Launcher CLI"]:::extrt
+ App["SubMiner app (Electron main)"]:::app
+
+ App <-->|"mpv IPC socket · /tmp/subminer-socket
JSON commands + property observe"| Mpv
+ Launcher -->|"app control socket · /tmp/subminer-control-*
--start, --show-visible-overlay, …"| App
+ Plugin -->|"app control socket
spawn / attach"| App
+
+ style MpvProc fill:#363a4f,stroke:#494d64,color:#cad3f5
+```
+
+How these sockets are established during launch is covered in [Playback Startup Flow](./architecture#playback-startup-flow).
+
## Core Surfaces
| File | Role |
diff --git a/docs-site/mpv-plugin.md b/docs-site/mpv-plugin.md
index a73348ea..532b38f5 100644
--- a/docs-site/mpv-plugin.md
+++ b/docs-site/mpv-plugin.md
@@ -163,6 +163,8 @@ script-message subminer-start backend=hyprland socket=/custom/path texthooker=no
## Lifecycle
+For how the plugin's auto-start fits into the full launch sequence — including when the launcher starts the overlay instead of the plugin — see [Playback Startup Flow](./architecture#playback-startup-flow).
+
- **File loaded**: If `auto_start=yes`, the plugin starts the overlay, then defers AniSkip lookup until after startup delay.
- **Auto-start pause gate**: If `auto_start_visible_overlay=yes` and `auto_start_pause_until_ready=yes`, launcher starts mpv paused and the plugin resumes playback after SubMiner reports tokenization-ready (with timeout fallback).
- **Duplicate auto-start events**: Repeated `file-loaded` hooks while overlay is already running are ignored for auto-start triggers (prevents duplicate start attempts).
diff --git a/docs-site/troubleshooting.md b/docs-site/troubleshooting.md
index 0cd58aa5..10f06e6f 100644
--- a/docs-site/troubleshooting.md
+++ b/docs-site/troubleshooting.md
@@ -14,6 +14,8 @@ SubMiner connects to mpv via a Unix socket (or named pipe on Windows). If the so
SubMiner retries the connection automatically with increasing delays (200 ms, 500 ms, 1 s, 2 s on first connect; 1 s, 2 s, 5 s, 10 s on reconnect). If mpv exits and restarts, the overlay reconnects without needing a restart.
+If the overlay never appears at all, see [Playback Startup Flow](./architecture#playback-startup-flow) for how a managed launch starts mpv and brings up the overlay.
+
## Logging and App Mode
- Default log output is `info`.