mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
feat: bind overlay state to secondary subtitle mpv visibility
This commit is contained in:
@@ -78,7 +78,7 @@ src/
|
||||
|
||||
### Service Layer (`src/core/services/`)
|
||||
|
||||
- **Overlay/window runtime:** `overlay-manager.ts`, `overlay-window.ts`, `overlay-window-geometry.ts`, `overlay-visibility.ts`, `overlay-bridge.ts`, `overlay-runtime-init.ts`, `overlay-content-measurement.ts`, `overlay-drop.ts`
|
||||
- **Overlay/window runtime:** `overlay-manager.ts`, `overlay-window.ts`, `overlay-visibility.ts`, `overlay-bridge.ts`, `overlay-runtime-init.ts`, `overlay-content-measurement.ts`, `overlay-drop.ts`
|
||||
- **Shortcuts/input:** `shortcut.ts`, `overlay-shortcut.ts`, `overlay-shortcut-handler.ts`, `shortcut-fallback.ts`, `numeric-shortcut.ts`
|
||||
- **MPV runtime:** `mpv.ts`, `mpv-transport.ts`, `mpv-protocol.ts`, `mpv-properties.ts`, `mpv-render-metrics.ts`
|
||||
- **Mining + Anki/Jimaku runtime:** `mining.ts`, `field-grouping.ts`, `field-grouping-overlay.ts`, `anki-jimaku.ts`, `anki-jimaku-ipc.ts`
|
||||
@@ -102,7 +102,6 @@ src/renderer/
|
||||
positioning.ts # Facade export for positioning controller
|
||||
positioning/
|
||||
controller.ts # Position controller orchestration
|
||||
invisible-layout*.ts # Invisible layer layout computations
|
||||
position-state.ts # Position state helpers
|
||||
handlers/
|
||||
keyboard.ts # Keybindings, chord handling, modal key routing
|
||||
@@ -125,7 +124,7 @@ src/renderer/
|
||||
|
||||
## Flow Diagram
|
||||
|
||||
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.
|
||||
The main process orchestrates a single primary overlay window plus modal surfaces: `main.ts` delegates to composition modules that wire together domain services. Subtitle layers (primary + secondary bar) are rendered in the same overlay renderer process, connected through `preload.ts`. External runtimes (launcher CLI and mpv plugin) operate independently and communicate via IPC socket or CLI passthrough.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
@@ -162,7 +161,7 @@ flowchart LR
|
||||
|
||||
subgraph Svc["Services — src/core/services/"]
|
||||
Mpv["MPV Stack<br/>transport · protocol<br/>properties · metrics"]:::svc
|
||||
Overlay["Overlay Manager<br/>window · geometry<br/>visibility · bridge"]:::svc
|
||||
Overlay["Overlay Manager<br/>window · visibility · bridge"]:::svc
|
||||
Mining["Mining & Subtitles<br/>mining · field-grouping<br/>subtitle-ws · tokenizer"]:::svc
|
||||
Integrations["Integrations<br/>jimaku · subsync<br/>texthooker · yomitan"]:::svc
|
||||
Tracking["Tracking<br/>anilist · jellyfin<br/>immersion · discord"]:::svc
|
||||
@@ -172,9 +171,7 @@ flowchart LR
|
||||
Bridge(["preload.ts<br/>Electron IPC"]):::bridge
|
||||
|
||||
subgraph Rend["Renderer — src/renderer/"]
|
||||
Visible["Visible window<br/>Yomitan lookups"]:::rend
|
||||
Invisible["Invisible window<br/>mpv positioning"]:::rend
|
||||
Secondary["Secondary window<br/>subtitle bar"]:::rend
|
||||
Overlay["Main overlay window<br/>primary + secondary subtitles"]:::rend
|
||||
UI["subtitle-render<br/>positioning<br/>handlers · modals"]:::rend
|
||||
end
|
||||
|
||||
@@ -193,10 +190,8 @@ flowchart LR
|
||||
DiscordExt <-->|"RPC"| Integrations
|
||||
|
||||
Overlay & Mining --> Bridge
|
||||
Bridge --> Visible
|
||||
Bridge --> Invisible
|
||||
Bridge --> Secondary
|
||||
Visible & Invisible & Secondary --> UI
|
||||
Bridge --> Overlay
|
||||
Overlay --> UI
|
||||
|
||||
style Comp fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
style Svc fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
@@ -264,9 +259,9 @@ For domains migrated to reducer-style transitions (for example AniList token/que
|
||||
- **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.
|
||||
- **Overlay runtime:** `initializeOverlayRuntime()` creates the primary overlay window (interactive Yomitan lookups and subtitle rendering) and registers global shortcuts and bounds tracking via the active 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.
|
||||
- **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 are sent to the main overlay renderer and modal surfaces.
|
||||
- **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
|
||||
@@ -298,14 +293,10 @@ flowchart LR
|
||||
|
||||
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 --> MainWin["Main overlay window<br/>primary + secondary subtitles"]:::init
|
||||
OverlayInit --> Shortcuts["Register global<br/>shortcuts"]:::init
|
||||
|
||||
VisWin --> Warmups
|
||||
InvWin --> Warmups
|
||||
SecWin --> Warmups
|
||||
MainWin --> Warmups
|
||||
Shortcuts --> Warmups
|
||||
|
||||
Warmups["Background<br/>warmups"]:::phase
|
||||
@@ -330,7 +321,7 @@ flowchart LR
|
||||
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
|
||||
Process --> Broadcast["Update AppState<br/>broadcast to renderer + modals"]:::runtime
|
||||
end
|
||||
|
||||
WarmupGroup --> Loop
|
||||
|
||||
@@ -38,8 +38,8 @@ features:
|
||||
- icon:
|
||||
src: /assets/dual-layer.svg
|
||||
alt: Dual layer icon
|
||||
title: Three-Plane Overlay Stack
|
||||
details: Secondary context plane + visible interactive layer + invisible interaction plane, each with independent behavior and startup state.
|
||||
title: Unified Overlay Stack
|
||||
details: Primary interactive subtitle layer with a built-in secondary context bar, all in one overlay window.
|
||||
- icon:
|
||||
src: /assets/highlight.svg
|
||||
alt: Highlight icon
|
||||
|
||||
@@ -24,11 +24,11 @@ SubMiner prioritizes subtitle responsiveness over heavy initialization:
|
||||
|
||||
This keeps early playback snappy and avoids mpv-side sluggishness while startup work completes.
|
||||
|
||||
## The Three Overlay Planes
|
||||
## Overlay Model
|
||||
|
||||
SubMiner uses three overlay planes, each serving a different purpose.
|
||||
SubMiner uses one overlay window with modal surfaces.
|
||||
|
||||
### Visible Overlay
|
||||
### Primary Subtitle Layer
|
||||
|
||||
The visible overlay renders subtitles as tokenized, clickable word spans. Each word is a separate element with reading and headword data attached. This plane is styled independently from mpv subtitles and supports:
|
||||
|
||||
@@ -38,31 +38,17 @@ The visible overlay renders subtitles as tokenized, clickable word spans. Each w
|
||||
- Modal dialogs for Jimaku search, field grouping, subsync, and runtime options
|
||||
- **N+1 highlighting** — known words from your Anki deck are visually highlighted
|
||||
|
||||
Toggle with `Alt+Shift+O` (global) or `y-t` (mpv plugin).
|
||||
Toggle visibility with `Alt+Shift+O` (global) or `y-t` (mpv plugin).
|
||||
|
||||
### Secondary Subtitle Plane
|
||||
### Secondary Subtitle Bar
|
||||
|
||||
The secondary plane is a compact top-strip layer for translation and context visibility while keeping primary reading flow below. It mirrors your configured secondary subtitle preference and can be independently shown or hidden.
|
||||
The secondary subtitle bar is a compact top-strip region in the same overlay window for translation/context visibility while keeping primary reading flow below. It mirrors your configured secondary subtitle preference and can be independently shown or hidden.
|
||||
|
||||
It is controlled by `secondarySub` configuration and shares lifecycle with the overlay stack.
|
||||
It is controlled by `secondarySub` configuration and shares lifecycle with the main overlay window.
|
||||
|
||||
### Invisible Overlay
|
||||
### Modal Surfaces
|
||||
|
||||
The invisible overlay is a transparent layer aligned with mpv's own subtitle rendering. It uses mpv's subtitle metrics (font size, margins, position, scaling) to map click targets accurately.
|
||||
|
||||
This layer still supports:
|
||||
|
||||
- Word-level click-through lookups over the text region
|
||||
- Optional manual position fine-tuning in pixel mode
|
||||
- Independent toggle behavior with global shortcuts
|
||||
|
||||
Position edit mode is available via `Ctrl/Cmd+Shift+P`, then arrow keys / `hjkl` to nudge position; `Shift` moves faster. Save with `Enter` or `Ctrl+S`, cancel with `Esc`.
|
||||
|
||||
Toggle controls:
|
||||
|
||||
- `Alt+Shift+O` / `y-t`: visible overlay
|
||||
- `Alt+Shift+I` / `y-i`: invisible overlay
|
||||
- Secondary plane visibility is controlled via `secondarySub` config and matching global shortcuts.
|
||||
Jimaku search, field-grouping, runtime options, and manual subsync open as modal surfaces on top of the same overlay window.
|
||||
|
||||
## Looking Up Words
|
||||
|
||||
@@ -73,10 +59,10 @@ Toggle controls:
|
||||
3. Yomitan detects the text selection and opens its popup with dictionary results.
|
||||
4. From the Yomitan popup, you can add the word directly to Anki.
|
||||
|
||||
### On the Invisible Overlay
|
||||
### On Overlay Subtitles
|
||||
|
||||
1. The invisible layer sits over mpv's own subtitle text.
|
||||
2. Click on any word in the subtitle — SubMiner maps your click position to the underlying text.
|
||||
1. Subtitles are rendered directly in the overlay.
|
||||
2. Click on any word in the subtitle.
|
||||
3. On macOS, word selection happens automatically on hover.
|
||||
4. Yomitan popup appears for lookup and card creation.
|
||||
|
||||
|
||||
55
docs/plans/2026-02-26-secondary-subtitles-main-overlay.md
Normal file
55
docs/plans/2026-02-26-secondary-subtitles-main-overlay.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Secondary Subtitles Main Overlay Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Ensure secondary subtitles render in the unified main overlay window and remove stale secondary-window/layer paths.
|
||||
|
||||
**Architecture:** Keep secondary subtitle DOM in the shared renderer tree, rely on mode classes (`secondary-sub-hidden|visible|hover`) for visibility, and remove obsolete legacy overlay-layer assumptions. Preserve modal behavior and existing subtitle rendering flow.
|
||||
|
||||
**Tech Stack:** TypeScript, Electron renderer CSS/DOM, Bun test runner.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Add Regression Tests For Main Overlay Secondary Rendering
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/renderer/subtitle-render.test.ts`
|
||||
- Modify: `src/renderer/error-recovery.test.ts`
|
||||
|
||||
**Step 1: Write failing tests**
|
||||
- Assert stylesheet no longer hides secondary subtitles in `layer-visible`.
|
||||
- Assert renderer platform resolution ignores legacy `secondary` overlay layer.
|
||||
|
||||
**Step 2: Run tests to verify failures**
|
||||
|
||||
Run: `bun test src/renderer/subtitle-render.test.ts src/renderer/error-recovery.test.ts`
|
||||
Expected: FAIL on secondary subtitle hide rule + legacy secondary layer handling.
|
||||
|
||||
### Task 2: Remove Secondary-Window CSS/Routing Assumptions
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/renderer/style.css`
|
||||
- Modify: `src/renderer/utils/platform.ts`
|
||||
- Modify: `src/renderer/error-recovery.ts`
|
||||
- Modify: `src/types.ts`
|
||||
|
||||
**Step 1: Implement minimal changes**
|
||||
- Remove legacy forced hide on `#secondarySubContainer`.
|
||||
- Remove obsolete layer-specific secondary-subtitle CSS blocks.
|
||||
- Drop legacy `secondary` overlay-layer parsing path from renderer platform resolver.
|
||||
- Narrow related overlay layer type unions.
|
||||
|
||||
**Step 2: Run targeted tests**
|
||||
|
||||
Run: `bun test src/renderer/subtitle-render.test.ts src/renderer/error-recovery.test.ts`
|
||||
Expected: PASS.
|
||||
|
||||
### Task 3: Validate Wider Related Surface
|
||||
|
||||
**Files:**
|
||||
- No additional code changes required.
|
||||
|
||||
**Step 1: Run broader related tests**
|
||||
|
||||
Run: `bun test src/renderer/subtitle-render.test.ts src/renderer/error-recovery.test.ts src/main/runtime/overlay-window-runtime-handlers.test.ts src/main/runtime/overlay-window-factory.test.ts src/core/services/overlay-manager.test.ts`
|
||||
Expected: Renderer tests pass; report any unrelated pre-existing failures.
|
||||
Reference in New Issue
Block a user