Merge pull request #1 from ksyasuda/refactor

refactor runtime deps wiring and docs/config updates
This commit is contained in:
2026-02-15 02:38:18 -08:00
committed by GitHub
315 changed files with 21744 additions and 8449 deletions

63
.github/workflows/docs.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: Docs
on:
push:
branches: [main]
paths:
- 'docs/**'
- '.github/workflows/docs.yml'
- 'package.json'
- 'pnpm-lock.yaml'
workflow_dispatch:
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: pages
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build docs
run: pnpm run docs:build
- name: Setup Pages
uses: actions/configure-pages@v5
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: docs/.vitepress/dist
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4

2
.gitignore vendored
View File

@@ -29,3 +29,5 @@ environment.toml
.vitepress/dist/ .vitepress/dist/
docs/.vitepress/cache/ docs/.vitepress/cache/
docs/.vitepress/dist/ docs/.vitepress/dist/
test/*
.worktrees/

1
.gitmodules vendored
View File

@@ -1,3 +1,4 @@
[submodule "vendor/texthooker-ui"] [submodule "vendor/texthooker-ui"]
path = vendor/texthooker-ui path = vendor/texthooker-ui
url = https://github.com/ksyasuda/texthooker-ui.git url = https://github.com/ksyasuda/texthooker-ui.git
branch = subminer

29
AGENTS.md Normal file
View File

@@ -0,0 +1,29 @@
<!-- BACKLOG.MD MCP GUIDELINES START -->
<CRITICAL_INSTRUCTION>
## BACKLOG WORKFLOW INSTRUCTIONS
This project uses Backlog.md MCP for all task and project management activities.
**CRITICAL GUIDANCE**
- If your client supports MCP resources, read `backlog://workflow/overview` to understand when and how to use Backlog for this project.
- If your client only supports tools or the above request fails, call `backlog.get_workflow_overview()` tool to load the tool-oriented overview (it lists the matching guide tools).
- **First time working here?** Read the overview resource IMMEDIATELY to learn the workflow
- **Already familiar?** You should have the overview cached ("## Backlog.md Overview (MCP)")
- **When to read it**: BEFORE creating tasks, or when you're unsure whether to track work
These guides cover:
- Decision framework for when to create tasks
- Search-first workflow to avoid duplicates
- Links to detailed guides for task creation, execution, and finalization
- MCP tools reference
You MUST read the overview resource to understand the complete workflow. The information is NOT summarized here.
</CRITICAL_INSTRUCTION>
<!-- BACKLOG.MD MCP GUIDELINES END -->

View File

@@ -1,4 +1,4 @@
.PHONY: help deps build install build-linux build-macos build-macos-unsigned install-linux install-macos install-plugin uninstall uninstall-linux uninstall-macos print-dirs pretty ensure-pnpm generate-config generate-example-config docs-dev docs-build docs-preview .PHONY: help deps build install build-linux build-macos build-macos-unsigned clean install-linux install-macos install-plugin uninstall uninstall-linux uninstall-macos print-dirs pretty ensure-pnpm generate-config generate-example-config docs-dev docs docs-preview dev-start dev-start-macos dev-toggle dev-stop
APP_NAME := subminer APP_NAME := subminer
THEME_FILE := subminer.rasi THEME_FILE := subminer.rasi
@@ -48,8 +48,13 @@ help:
" build-linux Build Linux AppImage" \ " build-linux Build Linux AppImage" \
" build-macos Build macOS DMG/ZIP (signed if configured)" \ " build-macos Build macOS DMG/ZIP (signed if configured)" \
" build-macos-unsigned Build macOS DMG/ZIP without signing/notarization" \ " build-macos-unsigned Build macOS DMG/ZIP without signing/notarization" \
" clean Remove build artifacts (dist/, release/, AppImage, binary)" \
" dev-start Build and launch local Electron app" \
" dev-start-macos Build and launch local Electron app with macOS tracker backend" \
" dev-toggle Toggle overlay in a running local Electron app" \
" dev-stop Stop a running local Electron app" \
" docs-dev Run VitePress docs dev server" \ " docs-dev Run VitePress docs dev server" \
" docs-build Build VitePress static docs" \ " docs Build VitePress static docs" \
" docs-preview Preview built VitePress docs" \ " docs-preview Preview built VitePress docs" \
" install-linux Install Linux wrapper/theme/app artifacts" \ " install-linux Install Linux wrapper/theme/app artifacts" \
" install-macos Install macOS wrapper/theme/app artifacts" \ " install-macos Install macOS wrapper/theme/app artifacts" \
@@ -126,6 +131,13 @@ build-macos-unsigned: deps
@pnpm -C vendor/texthooker-ui build @pnpm -C vendor/texthooker-ui build
@pnpm run build:mac:unsigned @pnpm run build:mac:unsigned
clean:
@printf '%s\n' "[INFO] Removing build artifacts"
@rm -f release/SubMiner-*.AppImage
@rm -f release/linux-unpacked/SubMiner
@rm -f "$(BINDIR)/subminer" "$(BINDIR)/SubMiner.AppImage"
@rm -rf dist release
generate-config: ensure-pnpm generate-config: ensure-pnpm
@pnpm run build @pnpm run build
@pnpm exec electron . --generate-config @pnpm exec electron . --generate-config
@@ -137,12 +149,26 @@ generate-example-config: ensure-pnpm
docs-dev: ensure-pnpm docs-dev: ensure-pnpm
@pnpm run docs:dev @pnpm run docs:dev
docs-build: ensure-pnpm docs: ensure-pnpm
@pnpm run docs:build @pnpm run docs:build
docs-preview: ensure-pnpm docs-preview: ensure-pnpm
@pnpm run docs:preview @pnpm run docs:preview
dev-start: ensure-pnpm
@pnpm run build
@pnpm exec electron . --start
dev-start-macos: ensure-pnpm
@pnpm run build
@pnpm exec electron . --start --backend macos
dev-toggle: ensure-pnpm
@pnpm exec electron . --toggle
dev-stop: ensure-pnpm
@pnpm exec electron . --stop
install-linux: install-linux:
@printf '%s\n' "[INFO] Installing Linux wrapper/theme artifacts" @printf '%s\n' "[INFO] Installing Linux wrapper/theme artifacts"

View File

@@ -1,53 +0,0 @@
# Overlay Positioning Flow
## 1) Window Bounds Flow (visible + invisible Electron windows)
```mermaid
flowchart LR
A[Platform backend selection<br/>src/window-trackers/index.ts] --> B[Tracker emits geometry<br/>onWindowFound/onGeometryChange]
B --> C[updateOverlayBounds<br/>src/main.ts]
C --> D[mainWindow.setBounds]
C --> E[invisibleWindow.setBounds]
```
## 2) Invisible Subtitle Layout Flow (mpv render metrics -> DOM layout)
```mermaid
flowchart LR
A[mpv property changes<br/>sub-pos, sub-font-size, osd-dimensions, etc.] --> B[MpvIpcClient parses events<br/>src/main.ts]
B --> C[updateMpvSubtitleRenderMetrics<br/>src/main.ts]
C --> D[broadcast mpv-subtitle-render-metrics:set]
D --> E[preload onMpvSubtitleRenderMetrics<br/>src/preload.ts]
E --> F[renderer receives metrics event<br/>src/renderer/renderer.ts]
F --> G[applyInvisibleSubtitleLayoutFromMpvMetrics]
G --> H[subtitleContainer/subtitleRoot inline styles updated]
```
## 3) Visible Subtitle Manual Position Flow
```mermaid
flowchart LR
A[User right-click drags subtitle] --> B[setupDragging<br/>src/renderer/renderer.ts]
B --> C[applyYPercent]
C --> D[saveSubtitlePosition IPC]
D --> E[saveSubtitlePosition in main<br/>src/main.ts]
E --> F[load/broadcast subtitle-position:set]
F --> G[applyStoredSubtitlePosition in renderer]
```
## 4) Fallback Bounds Flow (tracker not ready)
```mermaid
flowchart LR
A[windowTracker exists but not tracking] --> B["screen.getDisplayNearestPoint(cursor)"]
B --> C[display.workArea]
C --> D[updateOverlayBounds]
```
## Key Files
- `src/main.ts`
- `src/renderer/renderer.ts`
- `src/preload.ts`
- `src/window-trackers/base-tracker.ts`
- `src/window-trackers/index.ts`

View File

@@ -11,8 +11,9 @@ An all-in-one sentence mining overlay for MPV with AnkiConnect and dictionary (Y
- Yomitan integration for fast, on-screen lookups - Yomitan integration for fast, on-screen lookups
- Japanese text tokenization using MeCab with smart word boundary detection - Japanese text tokenization using MeCab with smart word boundary detection
- Integrated texthooker-ui server for use with Yomitan - Integrated texthooker-ui server for use with Yomitan
- Integrated websocket server (if [mpv_websocket](https://github.com/kuroahna/mpv_websocket) is not found) to send lines to the texthooker - Integrated WebSocket server (if [mpv_websocket](https://github.com/kuroahna/mpv_websocket) is not found) to send lines to the texthooker
- AnkiConnect integration for automatic card creation with media (audio/image) - AnkiConnect integration for automatic card creation with media (audio/image)
- Invisible subtitle position edit mode (`Ctrl/Cmd+Shift+P`, arrow keys to adjust, `Enter`/`Ctrl+S` save, `Esc` cancel)
## Demo ## Demo
@@ -31,29 +32,31 @@ Optional but recommended: `yt-dlp`, `fzf`, `rofi`, `chafa`, `ffmpegthumbnailer`.
### Linux (AppImage) ### Linux (AppImage)
Download the latest release from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest):
```bash ```bash
wget https://github.com/sudacode/subminer/releases/download/v1.0.0/subminer-1.0.0.AppImage -O ~/.local/bin/subminer.AppImage wget https://github.com/ksyasuda/SubMiner/releases/download/v0.1.0/SubMiner-0.1.0.AppImage -O ~/.local/bin/SubMiner.AppImage
chmod +x ~/.local/bin/subminer.AppImage chmod +x ~/.local/bin/SubMiner.AppImage
wget https://github.com/sudacode/subminer/releases/download/v1.0.0/subminer -O ~/.local/bin/subminer wget https://github.com/ksyasuda/SubMiner/releases/download/v0.1.0/subminer -O ~/.local/bin/subminer
chmod +x ~/.local/bin/subminer chmod +x ~/.local/bin/subminer
``` ```
`subminer` uses a [Bun](https://bun.com) shebang, so `bun` must be on `PATH`. The `subminer` wrapper uses a [Bun](https://bun.sh) shebang, so `bun` must be on `PATH`.
### From source ### From Source
```bash ```bash
git clone https://github.com/sudacode/subminer.git git clone https://github.com/ksyasuda/SubMiner.git
cd subminer cd SubMiner
make build make build
make install make install
``` ```
For macOS app bundle / signing / permissions details, use `docs/installation.md`. For macOS builds, signing, and platform-specific details, see [docs/installation.md](docs/installation.md).
## Quick Start ## Quick Start
1. Copy and customize [`config.example.jsonc`](config.example.jsonc) to `~/.config/SubMiner/config.jsonc`. 1. Copy and customize [`config.example.jsonc`](config.example.jsonc) to `$XDG_CONFIG_HOME/SubMiner/config.jsonc` (or `~/.config/SubMiner/config.jsonc`).
2. Start mpv with IPC enabled: 2. Start mpv with IPC enabled:
```bash ```bash
@@ -85,28 +88,34 @@ subminer -T video.mkv # disable texthooker
```bash ```bash
cp plugin/subminer.lua ~/.config/mpv/scripts/ cp plugin/subminer.lua ~/.config/mpv/scripts/
cp plugin/subminer.conf ~/.config/mpv/script-opts/ cp plugin/subminer.conf ~/.config/mpv/script-opts/
# or: make install-plugin
``` ```
Requires mpv IPC: `--input-ipc-server=/tmp/subminer-socket` Requires mpv IPC: `--input-ipc-server=/tmp/subminer-socket`
Default chord prefix: `y` (`y-y` menu, `y-s` start, `y-S` stop, `y-t` toggle visible layer). Default chord prefix: `y` (`y-y` menu, `y-s` start, `y-S` stop, `y-t` toggle visible layer).
Overlay Jimaku shortcut default: `Ctrl+Alt+J` (`shortcuts.openJimaku`). Overlay Jimaku shortcut default: `Ctrl+Shift+J` (`shortcuts.openJimaku`).
## Documentation ## Documentation
Detailed guides live in [`docs/`](docs/README.md): Detailed guides live in [`docs/`](docs/README.md):
- [Installation](docs/installation.md) - [Installation](docs/installation.md) — Platform requirements, AppImage/macOS/source installs, mpv plugin
- [Usage](docs/usage.md) - [Usage](docs/usage.md) — Script vs plugin workflow, keybindings, YouTube playback
- [Configuration](docs/configuration.md) - [Mining Workflow](docs/mining-workflow.md) — End-to-end mining guide, overlay layers, card creation
- [Development](docs/development.md) - [Configuration](docs/configuration.md) — Full config reference and option details
- [Anki Integration](docs/anki-integration.md) — AnkiConnect setup, field mapping, media generation
- [MPV Plugin](docs/mpv-plugin.md) — Chord keybindings, subminer.conf options, script messages
- [Troubleshooting](docs/troubleshooting.md) — Common issues and solutions
- [Development](docs/development.md) — Building, testing, contributing
- [Architecture](docs/architecture.md) — Service-oriented design, composition model, and modular renderer layout (`src/renderer/{modals,handlers,utils,...}`)
### Third-Party Components ### Third-Party Components
This project includes the following third-party components: This project includes the following third-party components:
- **[Yomitan](https://github.com/yomidevs/yomitan)** - GPL-3.0 - **[Yomitan](https://github.com/yomidevs/yomitan)** GPL-3.0
- **[texthooker-ui](https://github.com/Renji-XD/texthooker-ui)** - MIT - **[texthooker-ui](https://github.com/Renji-XD/texthooker-ui)** MIT
### Acknowledgments ### Acknowledgments

View File

@@ -0,0 +1,8 @@
---
id: m-0
title: "Codebase Clarity & Composability"
---
## Description
Improvements to code clarity, simplicity, and composability identified during the Feb 2026 codebase review. Focus on reducing monolithic files, eliminating duplication, and improving architectural boundaries.

View File

@@ -0,0 +1,43 @@
---
id: TASK-19
title: Enable overlay keybinds whenever app runtime is active
status: To Do
assignee: []
created_date: '2026-02-12 08:47'
updated_date: '2026-02-12 09:40'
labels: []
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Restored task after accidental cleanup. Ensure keybindings are available whenever the Electron runtime is active, while respecting focused overlay/input contexts that require local key handling.
<!-- SECTION:DESCRIPTION:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Started implementation: tracing overlay shortcut registration lifecycle and runtime activation gating.
Root cause: overlay shortcut sync executes during overlay runtime initialization before overlayRuntimeInitialized is set true, so registration could be skipped until a later visibility toggle.
Implemented fix in initializeOverlayRuntime: after setting overlayRuntimeInitialized = true, immediately call syncOverlayShortcuts() to register overlay keybinds for active runtime state.
Follow-up from repro: startup path with --start did not initialize overlay runtime (commandNeedsOverlayRuntime excluded start), so overlay keybinds stayed unavailable until first overlay visibility command.
Updated CLI runtime gating so --start initializes overlay runtime, which activates overlay shortcut registration immediately.
User clarified MPV-only workflow requirement. Added MPV plugin keybindings and script messages for mining/runtime actions (copy/mine/multi/mode/field-grouping/subsync/audio-card/runtime-options) so these actions are available from mpv chord bindings without relying on overlay global shortcuts.
Per user direction, reverted all shortcut/runtime/plugin changes from this implementation cycle. Desired behavior is to keep keybindings working only when overlay is active.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Ensured overlay shortcuts are available as soon as overlay runtime becomes active by resyncing after activation flag is set. This prevents startup states where shortcuts remained inactive until a later overlay visibility change.
Follow-up fix: included --start in overlay-runtime-required commands so keybinds are active right after startup, even before toggling visible/invisible overlays.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,26 @@
---
id: TASK-20
title: Implement content-bounded overlay windows and decoupled secondary top bar
status: To Do
assignee: []
created_date: '2026-02-12 08:47'
updated_date: '2026-02-12 09:42'
labels: []
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Implement the overlay sizing redesign documented in `overlay_window.md`: move visible/invisible overlays from fullscreen bounds to content-bounded sizing, and decouple secondary subtitle rendering into an independent top bar window/lifecycle.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Per-layer bounds ownership is implemented for overlay windows (no shared full-bounds setter for all layers).
- [ ] #2 Renderer-to-main IPC contract exists for measured overlay content bounds with layer identity and safe validation.
- [ ] #3 Visible and invisible overlays use content-bounded sizing with padding/clamp/jitter protections and full-bounds fallback when measurements are unavailable.
- [ ] #4 Secondary subtitle top bar is decoupled from primary overlay visibility and follows mode-specific behavior.
- [ ] #5 Automated tests and manual validation matrix cover wrapping, style changes, monitor moves, tracker churn, and simultaneous overlay states.
<!-- AC:END -->

View File

@@ -0,0 +1,28 @@
---
id: TASK-20.3
title: >-
Implement content-bounded sizing algorithm for visible and invisible overlay
windows
status: To Do
assignee: []
created_date: '2026-02-12 08:47'
updated_date: '2026-02-12 09:42'
labels: []
dependencies: []
parent_task_id: TASK-20
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Implement content-bounded sizing for visible/invisible windows using measured rects plus tracker origin, with robust clamping and jitter resistance.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Bounds algorithm applies configurable padding, minimum size, display-workarea clamp, and integer snap.
- [ ] #2 Main-process bounds updates are thresholded/debounced to reduce jitter and unnecessary `setBounds` churn.
- [ ] #3 When no valid measurement exists, layer falls back to safe tracker/display bounds without breaking interaction.
- [ ] #4 Visible+invisible overlays can coexist without full-window overlap/input conflicts caused by shared fullscreen bounds.
<!-- AC:END -->

View File

@@ -0,0 +1,26 @@
---
id: TASK-20.4
title: Implement dedicated secondary top-bar overlay window
status: To Do
assignee: []
created_date: '2026-02-12 09:43'
labels: []
dependencies: []
parent_task_id: TASK-20
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Create and integrate a dedicated secondary subtitle overlay window with independent lifecycle, z-order, bounds, and pointer policy, decoupled from primary visible/invisible overlay windows.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 A third overlay window dedicated to secondary subtitles is created and managed alongside existing visible/invisible windows.
- [ ] #2 Secondary window visibility follows secondary mode semantics (`hidden`/`visible`/`hover`) independent of primary overlay visibility.
- [ ] #3 Secondary subtitle text/mode/style updates are routed directly to the secondary window renderer path.
- [ ] #4 Pointer passthrough/interaction behavior for secondary window is explicit and does not regress existing hover/selection interactions.
- [ ] #5 Window cleanup/lifecycle (create, close, restore) integrates with existing overlay runtime lifecycle.
<!-- AC:END -->

View File

@@ -0,0 +1,26 @@
---
id: TASK-20.5
title: 'Add rollout guards, tests, and validation matrix for content-bounded overlays'
status: To Do
assignee: []
created_date: '2026-02-12 09:43'
labels: []
dependencies: []
parent_task_id: TASK-20
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add safety controls and verification coverage for the new content-bounded overlay architecture and secondary top-bar window.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Feature flag or equivalent rollout guard exists for switching to new sizing/window behavior.
- [ ] #2 Service-level/unit tests cover bounds clamping, jitter thresholding, invalid measurement fallback, and per-layer updates.
- [ ] #3 Manual validation checklist documents and verifies wrap/no-wrap, style changes, monitor moves, tracker churn, modal interactions, and simultaneous overlay states.
- [ ] #4 Regression checks confirm existing single-layer and startup/shutdown behavior remain stable.
- [ ] #5 Task includes explicit pass/fail notes from validation run(s).
<!-- AC:END -->

View File

@@ -0,0 +1,47 @@
---
id: TASK-30
title: Enable anime streaming via extension repos with configurable mpv playback flow
status: To Do
assignee: []
created_date: '2026-02-13 18:32'
updated_date: '2026-02-13 18:34'
labels: []
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add a feature to query configured extension repositories for anime titles/episodes from SubMiner, let users select a streamable source, and play it through mpv with minimal friction. The result should be interactive from Electron (and triggered from mpv via existing command bridge), and fully configurable in app config.
The implementation should provide a modular backend resolver and a clear UI flow that mirrors existing modal interaction patterns, while keeping mpv playback unchanged (use loadfile with resolved URL and optional headers/referrer metadata).
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Create a stable config schema for one or more extension source backends (repos/endpoints/flags) and persist in user config + default template.
- [ ] #2 Resolve an anime search term to candidate series from configured sources.
- [ ] #3 Resolve an episode selection to at least one playable stream URL candidate with playback metadata when available.
- [ ] #4 Provide an interactive user flow in the app to search/select episode and choose stream source.
- [ ] #5 Play the selected stream by invoking the existing mpv command path.
- [ ] #6 Support launching the flow from mpv interactions (keyboard/menu) by forwarding a command/event into the renderer modal flow.
- [ ] #7 Add error states for empty results, no playable source, and repository failures, with clear user messages.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Execution sequence for implementation: 1) TASK-30.1 (config/model), 2) TASK-30.2 (resolver), 3) TASK-30.3 (IPC), 4) TASK-30.4 (UI modal), 5) TASK-30.5 (mpv trigger), 6) TASK-30.6 (validation/rollout checklist).
Rollout recommendation: complete TASK-30.6 only after TASK-30.1-30.5 are done and can be verified in combination.
<!-- SECTION:NOTES:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Config schema validated in app startup (bad config surfaces clear error).
- [ ] #2 No hardcoded source/resolver URLs in UI layer; resolver details are backend-driven.
- [ ] #3 Play command path uses existing mpv IPC/runtime helpers.
- [ ] #4 Documentation includes how to configure extension repos and optional auth/headers.
- [ ] #5 Feature is gated behind a config flag and can be disabled cleanly.
<!-- DOD:END -->

View File

@@ -0,0 +1,40 @@
---
id: TASK-30.1
title: Design extension source config model and defaults
status: To Do
assignee: []
created_date: '2026-02-13 18:32'
updated_date: '2026-02-13 18:34'
labels: []
dependencies: []
parent_task_id: TASK-30
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Define a backend-agnostic configuration contract for extension repository streaming, including resolver endpoints/process mode, query/auth headers, timeouts, enable flags, and source preference. Wire schema through Config/ResolvedConfig and generated template/defaults so users can manage repos entirely through config.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Add new config sections for extension source providers in Config and ResolvedConfig types.
- [ ] #2 Add validation defaults and env-compatible parsing for provider list, auth, header overrides, and feature flags.
- [ ] #3 Update config template and docs text so defaults are discoverable and editable.
- [ ] #4 Invalid/missing config should fail fast with a clear message path.
- [ ] #5 Existing config readers do not regress when no providers are configured.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Phase 1 — Foundation: config contract + validation + defaults
<!-- SECTION:NOTES:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Config examples in template/docs include at least one provider entry shape.
- [ ] #2 Defaults remain backward-compatible when key is absent.
- [ ] #3 Feature can be disabled without touching unrelated settings.
<!-- DOD:END -->

View File

@@ -0,0 +1,42 @@
---
id: TASK-30.2
title: Implement extension resolver service (search + episode + stream resolution)
status: To Do
assignee: []
created_date: '2026-02-13 18:32'
updated_date: '2026-02-13 18:34'
labels: []
dependencies:
- TASK-30.1
parent_task_id: TASK-30
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Build a dedicated service in main process that queries configured extension repos and normalizes results into a unified internal model, including optional playback metadata. Keep transport abstracted so future backends (local process, remote API, Manatán-compatible source) can be swapped without changing renderer contracts.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Create a typed internal model for source, series, episode, and playable candidate with fields for quality/audio/headers/referrer/userAgent.
- [ ] #2 Implement provider abstraction with pluggable fetch/execution strategy from config.
- [ ] #3 Add services for searchAnime, listEpisodes, resolveStream (or equivalent) with cancellation/error boundaries.
- [ ] #4 Normalize all provider responses into deterministic field names and stable IDs.
- [ ] #5 Include resilient handling for empty/no-result/no-URL cases and network faults with explicit error categories.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Phase 2 — Core service: provider integration and stream resolution
<!-- SECTION:NOTES:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Resolver never leaks raw provider payload to renderer.
- [ ] #2 Streaming URL output includes reason for failure when unavailable.
- [ ] #3 Service boundaries allow unit-level validation of request/response mapping logic.
- [ ] #4 No blocking calls on Electron UI/main thread; all I/O is async and cancellable.
<!-- DOD:END -->

View File

@@ -0,0 +1,42 @@
---
id: TASK-30.3
title: Expose resolver operations via Electron IPC to renderer
status: To Do
assignee: []
created_date: '2026-02-13 18:32'
updated_date: '2026-02-13 18:34'
labels: []
dependencies:
- TASK-30.2
parent_task_id: TASK-30
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add a typed preload and main-IPC contract for streaming queries and playback resolution so the renderer can initiate search/list/resolve without embedding network/provider logic in UI code.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Define IPC handlers in main with input/output schema validation and timeouts.
- [ ] #2 Expose corresponding functions in preload `window.electronAPI` and ElectronAPI types.
- [ ] #3 Reuse existing mpv command channel for playback and add a dedicated request/response flow for resolver actions.
- [ ] #4 Implement safe serialization and error marshalling for resolver-specific failures.
- [ ] #5 Add runtime wiring and lifetime management in app startup/shutdown.
- [ ] #6 Document event/callback behavior for loading/error states.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Phase 3 — API surface: IPC/preload contract for resolver operations
<!-- SECTION:NOTES:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Renderer code can query providers without importing Node-only modules.
- [ ] #2 IPC paths have clear names and consistent response shapes across all calls.
- [ ] #3 Error paths return explicit machine-readable codes mapped to user-visible messages.
<!-- DOD:END -->

View File

@@ -0,0 +1,42 @@
---
id: TASK-30.4
title: 'Add interactive streaming modal (search, episode list, source selection, play)'
status: To Do
assignee: []
created_date: '2026-02-13 18:32'
updated_date: '2026-02-13 18:34'
labels: []
dependencies:
- TASK-30.3
parent_task_id: TASK-30
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Implement a renderer flow to query configured providers, display results, let user choose series and episode, and trigger playback for a selected stream. The UI should support keyboard interactions and surface backend errors clearly.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Create modal UI/state model for query, results list, selected item, episode list, candidate qualities, and loading/error status.
- [ ] #2 Wire renderer actions to new IPC methods for search/episode/resolve.
- [ ] #3 Render one-click or enter-to-play action that calls existing mpv playback pathway.
- [ ] #4 Persist minimal user preference (last provider/quality where possible) for faster repeat use.
- [ ] #5 Provide empty/error states and accessibility-friendly focus/keyboard navigation for lists.
- [ ] #6 Add a no-network mode fallback message when resolver calls fail.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Phase 4 — UX: interactive modal flow and playback callout
<!-- SECTION:NOTES:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Modal state is isolated and unsubscribes listeners on close.
- [ ] #2 No direct network logic in renderer beyond IPC calls.
- [ ] #3 Visual style and behavior are consistent with existing modal patterns.
<!-- DOD:END -->

View File

@@ -0,0 +1,45 @@
---
id: TASK-30.5
title: >-
Wire mpv script-message/shortcut trigger into streaming modal and playback
path
status: To Do
assignee: []
created_date: '2026-02-13 18:33'
updated_date: '2026-02-13 18:34'
labels: []
dependencies:
- TASK-30.3
- TASK-30.4
parent_task_id: TASK-30
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Allow users to open streaming selection from within mpv via keybind/menu and route that intent into renderer modal and playback flow without requiring separate window focus changes.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Add/extend mpv Lua plugin or existing command registry to emit a custom action for opening the streaming picker.
- [ ] #2 Handle this action in main IPC/mpv-command pipeline and forward to renderer modal state.
- [ ] #3 Add at least one default keybinding/menu entry documented in config/plugin notes.
- [ ] #4 Ensure playback launched from the in-player flow uses the same command path and error messages as renderer-initiated flow.
- [ ] #5 Add graceful handling when feature is disabled or no providers are configured.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Phase 5 — In-player entry: mpv trigger/menu integration
<!-- SECTION:NOTES:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 No duplicate mpv command parsing between picker and legacy commands.
- [ ] #2 Feature can be used in overlay and mpv-only mode where applicable.
- [ ] #3 No dependency on modal open state when launched by mpv trigger.
- [ ] #4 Manual and keybind invocations behave consistently.
<!-- DOD:END -->

View File

@@ -0,0 +1,45 @@
---
id: TASK-30.6
title: >-
Add integration validation plan and rollout checklist for anime streaming
feature
status: To Do
assignee: []
created_date: '2026-02-13 18:34'
updated_date: '2026-02-13 18:34'
labels: []
dependencies: []
parent_task_id: TASK-30
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Create a concrete validation task that defines end-to-end acceptance checks for config loading, resolver behavior, IPC contract correctness, UI flow, and mpv-triggered launch. The checklist should be actionable and align with existing project conventions so completion can be verified without guesswork.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Define test scenarios for config success/failure cases, including invalid provider config and feature disabled mode.
- [ ] #2 Define search/list/resolve API contract tests and error-code assertions (empty, timeout, auth error, no playable URL).
- [ ] #3 Define renderer UX checks for modal state transitions, loading indicators, empty results, selection, and play invocation.
- [ ] #4 Define in-mpv trigger checks for command/message pathway and fallback behavior when modal disabled/unavailable.
- [ ] #5 Define manual smoke steps for end-to-end play from query to mpv playback using at least one configured source.
- [ ] #6 Document expected logs/telemetry markers for troubleshooting and rollback.
- [ ] #7 Define rollback criteria and what constitutes safe partial completion.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Phase 6 — Validation: rollout, smoke tests, and release readiness checklist
<!-- SECTION:NOTES:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Checklist covers happy-path and failure-path for each task dependency.
- [ ] #2 Verification steps are executable without external tooling assumptions.
- [ ] #3 No task can be marked done without explicit evidence fields filled in.
- [ ] #4 Rollout and fallback behavior are documented per deployment/release phase.
<!-- DOD:END -->

View File

@@ -0,0 +1,34 @@
---
id: TASK-30.7
title: >-
Add English-source preference + hard-sub stripping workflow in Aniyomi
streaming path
status: To Do
assignee: []
created_date: '2026-02-13 18:41'
labels: []
dependencies:
- TASK-30.2
- TASK-30.5
parent_task_id: TASK-30
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Improve the Aniyomi/anime extension streaming flow to prefer English-capable sources with soft subtitles, and automatically recover when only hard-subbed streams are available by stripping embedded subtitles with ffmpeg and attaching external Jimaku subtitle files into mpv.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 During source scoring/selection, prefer providers/sources that declare or expose soft subtitles for English audio or subtitle tracks over hard-subbed alternatives.
- [ ] #2 Add a config option for preferred language targets (default English) and fallback policy (favor soft subtitles, then hard-sub fallback).
- [ ] #3 Detect when a resolved stream is hard-sub-only and a soft-sub source is unavailable for the same episode.
- [ ] #4 When hard subs are used, attempt to generate a subtitle-less playback stream path using ffmpeg and feed an external subtitle file (including Jimaku-provided `.ass/.srt`) into the mpv playback path.
- [ ] #5 Preserve stream selection metadata (language tags, subtitle type, availability state) for UI decisions and error messaging.
- [ ] #6 If ffmpeg conversion is not possible/disabled or fails, surface a clear status that explains fallback behavior instead of silent failure.
- [ ] #7 Integrate subtitle source preferences with Jimaku so user-fetched or resolved subtitles are preferred for burn-in removal cases where supported.
- [ ] #8 Add handling for unsupported codecs/containers and provider limitations so direct passthrough is still used when hard-sub stripping is unsafe.
- [ ] #9 Document new behavior in feature docs/FAQ: how sources are ranked, what hard-sub stripping does, and known compatibility limitations.
<!-- AC:END -->

View File

@@ -0,0 +1,31 @@
---
id: TASK-30.8
title: >-
Add observability and tuning metrics for Aniyomi subtitle-source fallback
decisions
status: To Do
assignee: []
created_date: '2026-02-13 18:41'
labels: []
dependencies:
- TASK-30.7
parent_task_id: TASK-30
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add lightweight telemetry/analytics hooks (local logs + optional structured counters) to measure how Aniyomi/anime streaming source selection behaves, including soft-sub preference, hard-sub fallback usage, and ffmpeg+Jimaku post-processing outcomes, to support source ranking tuning.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Track per-playback decision metadata including chosen source, language match score, subtitle mode (soft/hard), and reason for source preference ordering.
- [ ] #2 Emit success/failure counters for hard-sub stripping attempts (started/succeeded/failed/unsupported codec) with reason codes.
- [ ] #3 Log whether Jimaku subtitle attachment was available and successfully loaded for ffmpeg-assisted flows.
- [ ] #4 Capture user-visible fallback reasons when preferred English/soft-sub sources are absent and hard-sub path is used.
- [ ] #5 Add a debug/report view or log artifact with counters that can be reviewed in-app or via config/log files.
- [ ] #6 Document metrics definitions so developers can tune source scorer and fallback policy without code changes.
- [ ] #7 Ensure instrumentation has low overhead and is opt-out-safe with existing config flags.
<!-- AC:END -->

View File

@@ -0,0 +1,30 @@
---
id: TASK-30.9
title: Expose subtitle preference and ffmpeg fallback tuning controls in settings UI
status: To Do
assignee: []
created_date: '2026-02-13 18:42'
labels: []
dependencies:
- TASK-30.7
- TASK-30.8
parent_task_id: TASK-30
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add user-configurable controls for Aniyomi streaming subtitle behavior, including preferred language profile, soft-vs-hard source preference, ffmpeg-assisted hard-sub removal behavior, and policy toggles so quality and fallback behavior can be tuned without code changes.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Add settings UI fields to define preferred subtitle/audiotrack language order (e.g., en, ja) and enable/disable hard-sub fallback mode.
- [ ] #2 Add explicit toggle for enabling hard-sub stripping via ffmpeg and configurable timeout/quality limits to avoid long waits.
- [ ] #3 Expose source ranking preferences for soft-sub vs hard-sub sources and optional fallback to native/transcoded source when preferred modes are unavailable.
- [ ] #4 Persist settings in existing config schema with migration-safe defaults and include clear validation for invalid/unsupported values.
- [ ] #5 Show current effective policy in streaming UI (for debugging): source selected + reason + whether subtitles are soft/hard and if ffmpeg path is active.
- [ ] #6 Add user-facing explanatory text and warnings for ffmpeg dependency, expected CPU/cpu cost, and compatibility limits.
- [ ] #7 Log and display when user-adjusted policy changes alter a previously preferred source choice during runtime.
<!-- AC:END -->

View File

@@ -0,0 +1,45 @@
---
id: TASK-48
title: Add streaming mode integration in subminer using ani-cli stream source
status: To Do
assignee: []
created_date: '2026-02-14 06:01'
updated_date: '2026-02-14 08:19'
labels:
- stream
- ani-cli
- jimaku
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Implement a new streaming mode so SubMiner can resolve and play episodes via ani-cli stream sources instead of existing file/download flow. The mode is enabled with a CLI flag (`-s` / `--stream`) and, when active, should prefer streamed playback and subtitle handling that keeps Japanese (or configured primary) subtitles as the main track. If a stream lacks Japanese subtitle tracks, fetch and inject subtitles from Jimaku and set them as primary subtitles according to SubMiner config.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Add a command-line option `-s`/`--stream` that enables streaming mode and is documented in help/config UX.
- [ ] #2 When streaming mode is enabled, resolve episode/video URLs using existing ani-cli stream selection logic (ported into SubMiner) and route playback to the resolved stream source.
- [ ] #3 If stream metadata contains a Japanese subtitle track, preserve that track as the primary subtitle stream path per current primary-subtitle selection behavior.
- [ ] #4 If no Japanese subtitle track is present in the stream metadata, fetch matching subtitles from Jimaku and load them into playback.
- [ ] #5 When loading Jimaku subtitles, replace/overwrite existing non-Japanese primary subtitle track behavior so the language configured as primary in SubMiner config is used instead.
- [ ] #6 Non-streaming mode continues to follow current behavior when `-s`/`--stream` is not set.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Superseded by TASK-51. Streaming via ani-cli in subminer was removed due Cloudflare 403/unreliable source metadata; re-evaluate later if reintroduced behind a feature flag and redesigned resolver/metadata pipeline.
<!-- SECTION:NOTES:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 CLI accepts both `-s` and `--stream` and enables streaming-specific behavior.
- [ ] #2 Streaming mode resolves streams through migrated ani-cli logic.
- [ ] #3 Japanese subs are preferred from stream metadata when available; Jimaku fallback is used only when absent.
- [ ] #4 Primary subtitle language in config determines which language is treated as default stream subtitle track after fallback.
- [ ] #5 Behavior is verified via test or manual checklist and documented in task notes.
<!-- DOD:END -->

View File

@@ -0,0 +1,33 @@
---
id: TASK-48.1
title: Add streaming CLI flag plumbing and option wiring
status: To Do
assignee: []
created_date: '2026-02-14 06:03'
updated_date: '2026-02-14 08:19'
labels:
- stream
- cli
dependencies: []
parent_task_id: TASK-48
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add the `-s`/`--stream` option end-to-end in SubMiner CLI and configuration handling, including defaults, help text, parsing/validation, and explicit routing so streaming mode is only enabled when requested.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Introduce `-s` short option and `--stream` long option in CLI parsing without breaking existing flags.
- [ ] #2 When set, the resulting config state reflects streaming mode enabled and is propagated to playback/session startup.
- [ ] #3 When unset, behavior remains identical to current non-streaming flows.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Superseded by TASK-51. CLI stream mode work deferred until streaming architecture is revisited.
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,33 @@
---
id: TASK-48.2
title: Port ani-cli stream-resolution logic into SubMiner
status: To Do
assignee: []
created_date: '2026-02-14 06:03'
updated_date: '2026-02-14 08:19'
labels:
- stream
- ani-cli
dependencies: []
parent_task_id: TASK-48
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Implement stream URL resolution by porting ani-cli logic for selecting providers/episodes and obtaining playable stream URLs so SubMiner can consume stream sources directly.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Encapsulate stream search/provider selection logic in a dedicated module in SubMiner.
- [ ] #2 Resolve episode query input into a canonical playable stream URL in streaming mode.
- [ ] #3 Preserve existing behavior for non-streaming flow and expose errors when stream resolution fails.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Superseded by TASK-51. Stream URL resolution via ani-cli postponed; previous attempt exposed anti-bot/403 fragility and poor title-source reliability.
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,33 @@
---
id: TASK-48.3
title: Implement stream subtitle selection and primary-language precedence
status: To Do
assignee: []
created_date: '2026-02-14 06:03'
updated_date: '2026-02-14 08:19'
labels:
- stream
- subtitles
dependencies: []
parent_task_id: TASK-48
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Handle subtitle track selection for stream playback so Japanese (or configured primary language) subtitle behavior is correctly applied when stream metadata includes or omits JP tracks.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Use stream metadata to choose and mark the configured primary language subtitle as active when available.
- [ ] #2 If no matching primary language track exists in stream metadata, keep previous fallback behavior only for non-streaming mode.
- [ ] #3 When no Japanese track exists and config primary is different, explicitly set configured primary as primary track for streaming flow.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Superseded by TASK-51. Stream subtitle language precedence in streaming mode deferred with full design revisit.
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,34 @@
---
id: TASK-48.4
title: Add Jimaku subtitle fallback for stream mode
status: To Do
assignee: []
created_date: '2026-02-14 06:03'
updated_date: '2026-02-14 08:19'
labels:
- stream
- jimaku
- subtitles
dependencies: []
parent_task_id: TASK-48
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
When a resolved stream lacks JP/primary-language tracks, fetch subtitles from Jimaku and inject them for playback, overriding non-primary subtitle defaults in streaming mode according to config.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Detect missing primary subtitle from stream metadata and trigger Jimaku lookup for matching episode.
- [ ] #2 Load fetched Jimaku subtitles into playback pipeline and mark them as the primary subtitle track.
- [ ] #3 Fallback is only used in streaming mode and should not alter subtitle behavior outside streaming.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Superseded by TASK-51. Jimaku fallback for streams deferred along with entire streaming flow.
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,33 @@
---
id: TASK-48.5
title: Add verification plan/tests for streaming mode behavior
status: To Do
assignee: []
created_date: '2026-02-14 06:03'
updated_date: '2026-02-14 08:19'
labels:
- stream
- qa
dependencies: []
parent_task_id: TASK-48
priority: low
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Create a validation plan or tests for CLI flag behavior, stream resolution, and subtitle precedence/fallback rules so streaming mode changes are measurable and regressions are caught.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Document/manual checklist covers `-s` and `--stream` invocation and streaming-only behavior.
- [ ] #2 Include cases for (a) stream with JP subtitles, (b) no JP subtitles with Jimaku fallback, (c) primary-language not Japanese.
- [ ] #3 Run or provide reproducible checks to confirm non-streaming behavior unchanged.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Superseded by TASK-51. Verification plan moved to deferred reimplementation context.
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,49 @@
---
id: TASK-48.6
title: Wire -s/--stream mode to Ani-cli and Jimaku subtitle fallback
status: To Do
assignee: []
created_date: '2026-02-14 06:06'
updated_date: '2026-02-14 08:19'
labels: []
dependencies: []
references:
- ani-cli/ani-cli
- src/jimaku/utils.ts
- src/core/services/anki-jimaku-service.ts
documentation:
- ani-cli/README.md
- subminer
- src/cli/help.ts
parent_task_id: TASK-48
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Implement SubMiner streaming mode end-to-end behind a `-s`/`--stream` flag. In stream mode, use the vendored ani-cli resolution flow to get playable stream URLs instead of local file/YouTube URL handling. If resolved streams do not expose Japanese subtitles, fetch matching subtitles from Jimaku and load them into mpv as the active primary subtitle track, overwriting the current non-primary/non-Japanese default according to subminer primary-subtitle configuration.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 When `subminer -s` is used, resolution should pass a search/query through ani-cli stream logic and play the resolved stream source.
- [ ] #2 If the stream includes a Japanese subtitle track, preserve and select the configured primary subtitle language behavior without Jimaku injection.
- [ ] #3 If no Japanese (or configured primary language) subtitle exists in stream metadata, fetch and inject Jimaku subtitles before playback starts.
- [ ] #4 Loaded Jimaku subtitles should become the selected primary subtitle track, replacing any existing default non-primary subtitle in that context.
- [ ] #5 When `-s` is not passed, non-streaming behavior remains unchanged.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Superseded by TASK-51. End-to-end stream wiring to ani-cli is deferred.
<!-- SECTION:NOTES:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 CLI exposes both `-s` and `--stream` in help/config and validation.
- [ ] #2 Implementation includes a clear fallback path when stream subtitles are absent and Jimaku search/download fails gracefully.
- [ ] #3 Subtitles loading path avoids temp-file leaks; temporary media/subtitle artifacts are cleaned up on exit.
- [ ] #4 At least one verification step (manual or test) confirms stream mode path works for an episode with and without Japanese stream subtitles.
<!-- DOD:END -->

View File

@@ -0,0 +1,30 @@
---
id: TASK-51
title: Revisit Ani-cli stream mode integration later
status: To Do
assignee: []
created_date: '2026-02-14 08:19'
labels:
- someday
- streaming
- ani-cli
- feature-flag
dependencies: []
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Current codebase has removed ani-cli integration and stream-mode from subminer temporarily. Keep a deferred design task to reintroduce streaming mode in a future cycle.
Findings from prior attempts:
- `subminer -s <query>` path relied on `ani-cli` resolving stream URLs, but returned stream URLs that are Cloudflare-protected (`tools.fast4speed.rsvp`) and often returned 403 from mpv/ytdl-hook (generic anti-bot/Forbidden).
- Even after passing `ytdl` extractor args, stream playback via subminer still failed because URL/anti-bot handling differed from direct ani-cli execution context.
- We also observed stream title resolution issues: selected titles from ani-cli menu were unreliable/random and broke downstream Jimaku matching behavior.
- ffsubsync failures were difficult to debug initially due to OSD-only error visibility; logging was added to mpv log path.
- Based on these findings and instability, stream mode should be explicitly deferred rather than partially reintroduced.
Proposal:
- Reintroduce behind a feature flag / future milestone only.
- Re-design around a dedicated stream source resolver with robust URL acquisition and source metadata preservation (query/episode/title) before subtitle sync flows.
<!-- SECTION:DESCRIPTION:END -->

View File

@@ -0,0 +1,53 @@
---
id: TASK-10
title: Consolidate service naming conventions and barrel exports
status: Done
assignee: []
created_date: '2026-02-11 08:21'
updated_date: '2026-02-15 07:00'
labels:
- refactor
- services
- naming
milestone: Codebase Clarity & Composability
dependencies: []
references:
- src/core/services/index.ts
priority: low
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
The service layer has inconsistent naming:
- Some functions end in `Service`: `handleCliCommandService`, `loadSubtitlePositionService`
- Some end in `RuntimeService`: `replayCurrentSubtitleRuntimeService`, `sendMpvCommandRuntimeService`
- Some are plain: `shortcutMatchesInputForLocalFallback`
- Factory functions mix `create*DepsRuntimeService` with `create*Service`
The barrel export (src/core/services/index.ts) re-exports 79 symbols from 28 files through a single surface, which obscures dependency boundaries. Consumers import everything from `./core/services` and can't tell which service file they actually depend on.
Establish consistent naming:
- Exported service functions: `verbNounService` (e.g., `handleCliCommand`)
- Deps factory functions: `create*Deps`
- Consider whether the barrel re-export is still the right pattern vs direct imports from individual files.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 All service functions follow a consistent naming convention
- [ ] #2 Decision documented on barrel export vs direct imports
- [ ] #3 No functional changes
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Naming convention consolidation should be addressed as part of TASK-27.2 (split main.ts) and TASK-27.3 (anki-integration split). As modules are extracted and given clear boundaries, naming will be standardized at each boundary. No need to do a standalone naming pass — it's wasted effort if the module structure is about to change.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Subsumed by TASK-27.2 and TASK-27.3. Naming conventions were standardized at module boundaries during extraction. A standalone global naming pass would be churn with no structural benefit now that modules have clear ownership boundaries.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,44 @@
---
id: TASK-20.1
title: Refactor overlay runtime to use per-layer window bounds ownership
status: Done
assignee: []
created_date: '2026-02-12 08:47'
updated_date: '2026-02-13 08:04'
labels: []
dependencies: []
parent_task_id: TASK-20
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Refactor overlay runtime so each overlay layer owns and applies its bounds independently. Keep tracker geometry as shared origin input only.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 `updateOverlayBoundsService` no longer applies the same bounds to every overlay window by default.
- [x] #2 Main runtime/manager exposes per-layer bounds update paths for visible and invisible overlays.
- [x] #3 Window tracker updates feed shared origin data; each layer applies its own computed bounds.
- [x] #4 Single-layer behavior (visible-only or invisible-only) remains unchanged from user perspective.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Started implementation for per-layer overlay bounds ownership refactor.
Implemented per-layer bounds ownership path: visible and invisible layers now update bounds independently through overlay manager/runtime plumbing, while preserving existing geometry source behavior.
Replaced shared all-window bounds application with per-window bound application service and layer-specific runtime calls from visibility/tracker flows.
Archiving requested by user.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Refactored overlay bounds ownership to per-layer update paths. Tracker geometry remains shared input, but visible/invisible windows apply bounds independently via explicit layer routes. Existing single-layer UX behavior is preserved.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,45 @@
---
id: TASK-8
title: >-
Reduce MpvIpcClient deps interface and separate protocol from application
logic
status: Done
assignee: []
created_date: '2026-02-11 08:20'
updated_date: '2026-02-15 07:00'
labels:
- refactor
- mpv
- architecture
milestone: Codebase Clarity & Composability
dependencies: []
references:
- src/core/services/mpv-service.ts
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
MpvIpcClient (761 lines) in src/core/services/mpv-service.ts has a 22-property `MpvIpcClientDeps` interface that reaches back into main.ts state for application-level concerns (overlay visibility, subtitle timing, media path updates, OSD display).
The class mixes two responsibilities:
1. **IPC Protocol**: Socket connection, JSON message framing, reconnection, property observation
2. **Application Integration**: Subtitle text broadcasting, overlay visibility sync, timing tracking
Separating these would let the protocol layer be simpler and testable, while application-level reactions to mpv events could be handled by listeners/callbacks registered externally.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 MpvIpcClient deps interface reduced to protocol-level concerns only
- [ ] #2 Application-level reactions (subtitle broadcast, overlay sync, timing) handled via event emitter or external listeners
- [ ] #3 MpvIpcClient is testable without mocking 22 callbacks
- [ ] #4 Existing behavior preserved
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Superseded by TASK-27.4 which absorbed this task's full scope (protocol/application separation, deps interface reduction from 22 properties to protocol-level concerns, event-based app reactions). All acceptance criteria met by TASK-27.4.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,60 @@
---
id: TASK-9
title: Remove trivial wrapper functions from main.ts
status: Done
assignee: []
created_date: '2026-02-11 08:21'
updated_date: '2026-02-15 07:00'
labels:
- refactor
- main
- simplicity
milestone: Codebase Clarity & Composability
dependencies:
- TASK-7
references:
- src/main.ts
priority: low
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
main.ts contains many trivial single-line wrapper functions that add indirection without value:
```typescript
function getOverlayWindows(): BrowserWindow[] {
return overlayManager.getOverlayWindows();
}
function updateOverlayBounds(geometry: WindowGeometry): void {
updateOverlayBoundsService(geometry, () => getOverlayWindows());
}
function ensureOverlayWindowLevel(window: BrowserWindow): void {
ensureOverlayWindowLevelService(window);
}
```
Similarly, config accessor wrappers like `getJimakuLanguagePreference()`, `getJimakuMaxEntryResults()`, `resolveJimakuApiKey()` are pure boilerplate.
After TASK-7 (AppState container), many of these can be eliminated by having services access the state container directly, or by using the service functions directly at call sites without local wrappers.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Trivial pass-through wrappers eliminated (call service/manager directly)
- [ ] #2 Config accessor wrappers replaced with direct calls or a config accessor helper
- [ ] #3 main.ts line count reduced
- [ ] #4 No functional changes
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Priority changed from medium to low: this work is largely subsumed by TASK-27.2 (split main.ts). When main.ts is decomposed into composition-root modules, trivial wrappers will naturally be eliminated or inlined. Recommend folding remaining wrapper cleanup into TASK-27.2 rather than tracking separately. Keep this ticket as a checklist reference but don't execute independently.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Subsumed by TASK-27.2 (main.ts split). Trivial wrappers were eliminated or inlined as composition-root modules were extracted. main.ts reduced from ~2000+ LOC to 1384 with state routed through appState container. Standalone wrapper removal pass no longer needed.
<!-- SECTION:FINAL_SUMMARY:END -->

16
backlog/config.yml Normal file
View File

@@ -0,0 +1,16 @@
project_name: "SubMiner"
default_status: "To Do"
statuses: ["To Do", "In Progress", "Done"]
labels: []
milestones: []
date_format: yyyy-mm-dd
max_column_width: 20
default_editor: "nvim"
auto_open_browser: false
default_port: 6420
remote_operations: true
auto_commit: false
bypass_git_hooks: false
check_active_branches: true
active_branch_days: 30
task_prefix: "task"

View File

@@ -0,0 +1,8 @@
---
id: m-0
title: "Release v0.1.0"
---
## Description
Milestone: Release v0.1.0

View File

@@ -0,0 +1,49 @@
---
id: TASK-1
title: Refactor runtime services per plan.md
status: Done
assignee: []
created_date: '2026-02-10 18:46'
updated_date: '2026-02-11 03:35'
labels: []
dependencies: []
references:
- plan.md
ordinal: 1000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Execute the SubMiner refactoring initiative documented in plan.md to reduce thin abstractions, consolidate service boundaries, fix known quality issues, and increase test coverage while preserving current behavior.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Phase-based execution tasks are created and linked under this initiative.
- [x] #2 Each phase task includes clear, testable outcomes aligned with plan.md.
- [x] #3 Implementation proceeds with build/test verification checkpoints after each completed phase.
- [x] #4 Main behavior remains stable for startup, overlay, IPC, CLI, and tokenizer flows throughout refactor.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Created initiative subtasks TASK-1.1 through TASK-1.6 with phase-aligned acceptance criteria and sequential dependencies.
Completed TASK-1.1 (Phase 1 thin-wrapper removal) with green build/core tests.
Completed TASK-1.2 (Phase 2 DI adapter consolidation) with successful build and core test verification checkpoint.
Completed TASK-1.5 (critical behavior tests) with expanded tokenizer/mpv/subsync/CLI coverage and green core test suite.
Completed TASK-1.6 with documented no-go decision for optional domain-directory reorganization (kept current structure; tests remain green).
TASK-1.4 remains the only open phase, blocked on interactive desktop smoke checks that cannot be fully validated in this headless environment.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Completed the plan.md refactor initiative across Phases 1-5 and optional Phase 6 decisioning: removed thin wrappers, consolidated DI adapters and related services, fixed targeted runtime correctness issues, expanded critical behavior test coverage, and kept build/core tests green throughout. Final runtime smoke checks (start/toggle/trigger-field-grouping/stop) passed in this headless environment, with known limitation that visual overlay rendering itself was not directly inspectable.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,53 @@
---
id: TASK-1.1
title: 'Phase 1: Remove thin wrapper runtime services'
status: Done
assignee:
- codex
created_date: '2026-02-10 18:46'
updated_date: '2026-02-11 03:35'
labels: []
dependencies: []
references:
- plan.md
- src/main.ts
- src/core/services/index.ts
parent_task_id: TASK-1
ordinal: 11000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Inline trivial wrapper services into their call sites and delete redundant service/test files listed in Phase 1 of plan.md.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Wrapper logic from the Phase 1 file list is inlined at call sites without behavior changes.
- [x] #2 Phase 1 wrapper service files and corresponding trivial tests are removed from the codebase.
- [x] #3 `src/core/services/index.ts` exports are updated to remove deleted modules.
- [x] #4 `pnpm run build && pnpm run test:core` passes after Phase 1 completion.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Locate all Phase 1 wrapper service call sites and classify direct-inline substitutions vs orchestration-flow inlines.
2. Remove the lowest-risk wrappers first (`config-warning-runtime-service`, `app-logging-runtime-service`, `runtime-options-manager-runtime-service`, `overlay-modal-restore-service`, `overlay-send-service`) and update imports/exports.
3. Continue with startup and shutdown wrappers (`startup-resource-runtime-service`, `config-generation-runtime-service`, `app-shutdown-runtime-service`, `shortcut-ui-deps-runtime-service`) by inlining behavior into `main.ts` or direct callers.
4. Delete corresponding trivial test files for removed wrappers and clean `src/core/services/index.ts` exports.
5. Run `pnpm run build && pnpm run test:core`; fix regressions and update task notes/acceptance criteria incrementally.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Inlined wrapper behaviors into direct call sites in `main.ts` and `overlay-bridge-runtime-service.ts` for config warning/app logging, runtime options manager construction, generate-config bootstrap path, startup resource initialization, app shutdown sequence, overlay modal restore handling, overlay send behavior, and overlay shortcut local fallback invocation.
Deleted 16 Phase 1 files (9 wrapper services + 7 wrapper tests) and removed corresponding barrel exports from `src/core/services/index.ts`.
Updated `package.json` `test:core` list to remove deleted test entries so the script tracks current sources accurately.
Verification: `pnpm run build && pnpm run test:core` passes after refactor.
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,56 @@
---
id: TASK-1.2
title: 'Phase 2: Merge DI adapter runtime services into target services'
status: Done
assignee:
- codex
created_date: '2026-02-10 18:46'
updated_date: '2026-02-11 03:35'
labels: []
dependencies:
- TASK-1.1
references:
- plan.md
- src/core/services/cli-command-service.ts
- src/core/services/ipc-service.ts
- src/core/services/tokenizer-service.ts
- src/core/services/app-lifecycle-deps-runtime-service.ts
parent_task_id: TASK-1
ordinal: 9000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Absorb dependency adapter runtime services into core service modules and remove adapter files/tests while preserving runtime behavior.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 CLI, IPC, tokenizer, and app lifecycle adapter logic is merged into their target service modules.
- [x] #2 Adapter service and adapter test files listed in Phase 2 are removed.
- [x] #3 Callers pass dependency shapes expected by updated services without redundant mapping layers.
- [x] #4 `pnpm run build && pnpm run test:core` passes after Phase 2 completion.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Audit `cli-command-deps-runtime-service.ts`, `ipc-deps-runtime-service.ts`, `tokenizer-deps-runtime-service.ts`, and `app-lifecycle-deps-runtime-service.ts` usage sites in `main.ts` and corresponding services.
2. For each adapter, move null-guarding and shape-normalization logic into its target service (`cli-command-service.ts`, `ipc-service.ts`, `tokenizer-service.ts`, `app-lifecycle-service.ts`) and simplify caller dependency objects.
3. Remove adapter service files/tests and update `src/core/services/index.ts` exports/import sites.
4. Run `pnpm run build && pnpm run test:core` and fix any typing/regression issues from the interface consolidation.
5. Update task notes with dependency-shape decisions to preserve handoff clarity.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Merged adapter-constructor logic into target services: `createCliCommandDepsRuntimeService` moved into `cli-command-service.ts`, `createIpcDepsRuntimeService` moved into `ipc-service.ts`, `createTokenizerDepsRuntimeService` moved into `tokenizer-service.ts`, and `createAppLifecycleDepsRuntimeService` moved into `app-lifecycle-service.ts`.
Deleted adapter service files and tests for cli-command deps, ipc deps, tokenizer deps, and app lifecycle deps.
Updated `src/core/services/index.ts` exports and `package.json` `test:core` entries to remove deleted adapter test modules.
Verification: `pnpm run build && pnpm run test:core` passes after consolidation.
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,62 @@
---
id: TASK-1.3
title: 'Phase 3: Consolidate related service modules'
status: Done
assignee:
- codex
created_date: '2026-02-10 18:46'
updated_date: '2026-02-11 03:35'
labels: []
dependencies:
- TASK-1.2
references:
- plan.md
- src/core/services/overlay-visibility-service.ts
- src/core/services/overlay-manager-service.ts
- src/core/services/overlay-shortcut-service.ts
- src/core/services/numeric-shortcut-session-service.ts
- src/core/services/app-ready-runtime-service.ts
parent_task_id: TASK-1
ordinal: 5000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Merge split modules for overlay visibility, broadcast, shortcuts, numeric shortcuts, and startup orchestration into cohesive service files.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Overlay visibility/runtime split is consolidated into a single service module.
- [x] #2 Overlay broadcast functions are merged with overlay manager responsibilities.
- [x] #3 Shortcut and numeric shortcut runtime/lifecycle splits are consolidated as described in plan.md.
- [x] #4 Startup bootstrap and app-ready runtime orchestration is consolidated into one startup module.
- [x] #5 `pnpm run build && pnpm run test:core` passes after Phase 3 completion.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Merge `overlay-visibility-runtime-service.ts` exports into `overlay-visibility-service.ts` and update imports/exports.
2. Merge overlay broadcast responsibilities from `overlay-broadcast-runtime-service.ts` into `overlay-manager-service.ts` while preserving current APIs used by `main.ts`.
3. Consolidate shortcut modules by absorbing lifecycle utilities into `overlay-shortcut-service.ts` and fallback-runner logic into `overlay-shortcut-runtime-service.ts` (or successor handler module), then remove obsolete files.
4. Merge numeric shortcut runtime/session split into a single `numeric-shortcut-service.ts` and update call sites/tests.
5. Merge startup bootstrap + app-ready orchestration into a single startup module, update imports, remove obsolete files, and run `pnpm run build && pnpm run test:core`.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Merged overlay visibility runtime API into `overlay-visibility-service.ts` and removed `overlay-visibility-runtime-service.ts`.
Merged overlay broadcast behavior into `overlay-manager-service.ts` (including manager-level broadcasting) and removed `overlay-broadcast-runtime-service.ts` + test, with equivalent coverage moved into `overlay-manager-service.test.ts`.
Consolidated shortcut modules into `overlay-shortcut-service.ts` (lifecycle) and new `overlay-shortcut-handler.ts` (runtime handlers + local fallback), removing `overlay-shortcut-lifecycle-service.ts`, `overlay-shortcut-runtime-service.ts`, and `overlay-shortcut-fallback-runner.ts`.
Merged numeric shortcut runtime/session split into `numeric-shortcut-service.ts`; removed `numeric-shortcut-runtime-service.ts` and merged runtime test coverage into session tests.
Merged startup bootstrap + app-ready orchestration into `startup-service.ts`; removed `startup-bootstrap-runtime-service.ts` and `app-ready-runtime-service.ts` with tests updated to new module path.
Verification: `pnpm run build && pnpm run test:core` passes after consolidation.
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,89 @@
---
id: TASK-1.4
title: 'Phase 4: Fix runtime bugs and naming/code-quality issues'
status: Done
assignee:
- codex
created_date: '2026-02-10 18:46'
updated_date: '2026-02-11 03:35'
labels: []
dependencies:
- TASK-1.3
references:
- plan.md
- src/main.ts
- src/core/services/overlay-visibility-service.ts
- src/core/services/tokenizer-deps-runtime-service.ts
parent_task_id: TASK-1
ordinal: 2000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Address identified correctness and code-quality issues from plan.md, including race conditions, unsafe typing, callback rejection handling, and runtime naming cleanup.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Debug `console.log`/`console.warn` usage in overlay visibility logic is removed or replaced with structured logging where needed.
- [x] #2 Tokenizer type mismatch is fixed without unsafe `as never` casting.
- [x] #3 Field grouping resolver handling is made concurrency-safe against overlapping requests.
- [x] #4 Async callback wiring in CLI/IPC paths has explicit rejection handling.
- [x] #5 Remaining `-runtime-service` naming cleanup is completed without logic regressions.
- [x] #6 `pnpm run build && pnpm run test:core` passes and manual startup/overlay smoke checks succeed.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Remove or replace debug `console.log`/`console.warn` usage in `overlay-visibility-service.ts` while preserving useful operational logging semantics.
2. Confirm and fix unsafe tokenizer casting paths (already partially addressed during Phase 2) and ensure no remaining `as never` escape hatches in tokenizer dependency flows.
3. Make field grouping resolver handling in `main.ts` concurrency-safe by adding request sequencing and stale-resolution guards.
4. Audit async callback wiring in CLI/IPC integrations and add explicit rejection handling where promises are fire-and-forget.
5. Execute `pnpm run build && pnpm run test:core` and document manual smoke-test steps/outcomes.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Removed debug overlay-visibility `console.log`/`console.warn` statements from `overlay-visibility-service.ts`.
Eliminated unsafe tokenizer cast path during prior consolidation (`createTokenizerDepsRuntimeService` now uses typed `Token[]` and `mergeTokens(rawTokens)` without `as never`).
Added field-grouping overlap protection: `createFieldGroupingCallbackService` now cancels immediately when another resolver is active and only clears resolver state if the current resolver matches, preventing stale timeout/request cleanup from clobbering a newer resolver.
Added explicit rejection handling for async callback pathways: `shell.openExternal` now has `.catch(...)`; app lifecycle `whenReady` path now catches handler rejection; second-instance CLI dispatch is wrapped in try/catch logging.
Verification: `pnpm run build && pnpm run test:core` passes after these fixes.
Remaining in TASK-1.4: criterion #5 (`-runtime-service` naming cleanup batch) and criterion #6 manual smoke checks.
Completed `-runtime-service` naming cleanup for remaining modules by renaming files/tests and import paths, including: `overlay-bridge`, `field-grouping-overlay`, `mpv-control`, `runtime-options-ipc`, `mining`, `jimaku`, `anki-jimaku`, startup/app-ready test names, and subsync wrapper (`subsync-runner-service.ts`).
Resolved rename collision with existing `subsync-service.ts` by restoring original core subsync service from `HEAD` and moving runtime wrapper logic into `subsync-runner-service.ts`.
Verification after rename cleanup: `pnpm run build && pnpm run test:core` passes with updated test paths in `package.json`.
Manual smoke checks are still pending for criterion #6.
Smoke run attempt 1 (outside sandbox): `timeout 20s pnpm run start` started successfully, loaded config, initialized websocket/Mecab, and entered normal MPV reconnect loop when `/tmp/subminer-socket` was absent; no immediate startup crash after previous refactors.
Smoke run attempt 2 (outside sandbox): `timeout 20s pnpm exec electron . --start --auto-start-overlay` showed the same stable startup/reconnect behavior, but overlay activation could not be verified in this headless/non-interactive environment.
Manual GUI interactions (overlay render/toggle, mine card flow, field-grouping interaction) remain pending for a real desktop session with MPV running.
Automated interactive-smoke surrogate 1 (outside sandbox): started app, sent `--toggle`, then `--stop`; instance remained stable and cleanly stopped without crash.
Automated interactive-smoke surrogate 2 (outside sandbox): started app, sent `--trigger-field-grouping`, then `--stop`; command path executed without runtime crash and app shut down cleanly.
Observed expected reconnect behavior when MPV socket was absent (`ENOENT /tmp/subminer-socket`), with no regressions in startup/bootstrap flow.
Note: this environment is headless, so visual overlay rendering cannot be directly confirmed; command-path and process-lifecycle smoke checks passed.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Completed Phase 4 by removing debug logging noise, fixing unsafe typing and concurrency risks, adding async rejection handling, completing naming cleanup, and validating startup/command-path behavior through repeated build/test and live Electron smoke runs.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,75 @@
---
id: TASK-1.5
title: 'Phase 5: Add critical behavior tests for untested services'
status: Done
assignee:
- codex
created_date: '2026-02-10 18:46'
updated_date: '2026-02-11 03:35'
labels: []
dependencies:
- TASK-1.4
references:
- plan.md
- src/core/services/mpv-runtime-service.ts
- src/core/services/subsync-runtime-service.ts
- src/core/services/tokenizer-service.ts
- src/core/services/cli-command-service.ts
parent_task_id: TASK-1
ordinal: 4000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add meaningful behavior tests for high-risk services called out in plan.md: mpv, subsync, tokenizer, and expanded CLI command coverage.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 `mpv` service has focused tests for protocol parsing, event dispatch, request/response matching, reconnection, and subtitle extraction behavior.
- [x] #2 `subsync` service has focused tests for engine path resolution, command construction, timeout/error handling, and result parsing.
- [x] #3 `tokenizer` service has focused tests for parser readiness, token extraction, fallback behavior, and edge-case inputs.
- [x] #4 CLI command service tests cover all dispatch paths, async error propagation, and second-instance forwarding behavior.
- [x] #5 `pnpm run test:core` passes with all new tests green.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add focused tests for `tokenizer-service.ts` behavior (normalization, Yomitan-unavailable fallback, mecab fallback success/error paths, empty input handling).
2. Add focused tests for `subsync-service.ts` command/engine selection and failure handling using mocked command utilities where feasible.
3. Add focused tests for `mpv-service.ts` protocol handling (line parsing, request-response routing, property-change dispatch) with lightweight socket stubs.
4. Expand `cli-command-service.ts` tests for dispatch/error/second-instance forwarding edge paths not currently covered.
5. Run `pnpm run test:core` iteratively and update acceptance criteria as each service reaches meaningful coverage.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Added new tokenizer behavior tests in `src/core/services/tokenizer-service.test.ts` covering empty normalized input, newline normalization, mecab fallback success, and mecab error fallback-to-null.
Added new mpv protocol tests in `src/core/services/mpv-service.test.ts` covering JSON line-buffer parsing, property-change subtitle dispatch behavior, and request/response resolution by `request_id`.
Added new subsync workflow tests in `src/core/services/subsync-service.test.ts` covering already-running guard, manual-mode picker flow, and error propagation to OSD when MPV is unavailable.
Expanded `src/core/services/cli-command-service.test.ts` to cover socket/start dispatch, texthooker port override warning path, help-without-window shutdown, and async trigger-subsync error reporting.
Updated `package.json` `test:core` to include new/renamed test files; verification remains green with `pnpm run test:core` (17 tests total).
Expanded `mpv-service` tests with request rejection when disconnected, `requestProperty` error propagation, and pending-request disconnect resolution behavior.
Expanded `subsync-service` tests with manual alass source-track validation and auto-mode executable-path failure handling while ensuring in-progress state cleanup.
All updated tests remain green via `pnpm run test:core` after these additions.
Added Yomitan parser token-extraction coverage in `tokenizer-service.test.ts` (parser-available success path) in addition to fallback/edge-case tests.
Added MPV reconnection/request robustness tests (`scheduleReconnect`, disconnected request rejection, pending request disconnect resolution) to complement protocol/event/request-id tests in `mpv-service.test.ts`.
Added subsync command-construction tests using executable stubs for both engines (`ffsubsync` and `alass`) and validated success/failure result behavior; added timeout behavior coverage in `src/subsync/utils.test.ts` for child-process timeout handling used by subsync.
Expanded CLI dispatch tests with broad branch coverage for visibility/settings/copy/multi-copy/mining/open-runtime-options/stop/help/second-instance behaviors and async error propagation.
Verification: `pnpm run test:core` passes with 18 green tests including newly added `dist/subsync/utils.test.js`.
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,46 @@
---
id: TASK-1.6
title: 'Phase 6 (Optional): Reorganize services by domain directories'
status: Done
assignee: []
created_date: '2026-02-10 18:46'
updated_date: '2026-02-11 03:35'
labels: []
dependencies:
- TASK-1.5
references:
- plan.md
parent_task_id: TASK-1
ordinal: 3000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
If service flattening remains hard to navigate after Phases 1-5, optionally move modules into domain-based folders and update imports.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 A clear go/no-go decision for domain restructuring is documented based on post-phase-5 codebase state.
- [ ] #2 If executed, service modules are reorganized into domain folders with no import or runtime breakage.
- [x] #3 Build and core test commands pass after any directory reorganization.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Assess post-phase-5 directory complexity and determine whether domain reorganization is still justified.
2. If complexity remains acceptable, record a no-go decision and keep current structure stable.
3. If complexity is still problematic, perform import-safe domain reorganization and re-run build/tests.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Decision: no-go on Phase 6 directory reorganization for now. After Phases 1-5, service/module consolidation and test expansion have improved maintainability without introducing a high-risk import churn.
Rationale: preserving path stability now reduces regression risk while Phase 4 smoke validation remains open and large refactor commits are still stabilizing.
Verification baseline remains green (`pnpm run test:core`) with current structure.
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,46 @@
---
id: TASK-11
title: Break up the applyInvisibleSubtitleLayoutFromMpvMetrics mega function
status: To Do
assignee: []
created_date: '2026-02-11 08:21'
updated_date: '2026-02-15 07:00'
labels:
- refactor
- renderer
- complexity
milestone: Codebase Clarity & Composability
dependencies:
- TASK-27.5
references:
- src/renderer/renderer.ts
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
In renderer.ts (around lines 865-1075), `applyInvisibleSubtitleLayoutFromMpvMetrics` is a 211-line function with up to 5 levels of nesting. It handles OSD scaling calculations, platform-specific font compensation (macOS vs Linux), DPR calculations, ASS alignment tag interpretation (\an tags), baseline compensation, line-height fixes, font property application, and transform origin — all interleaved.
Extract into focused helpers:
- `calculateOsdScale(metrics, renderAreaHeight)` — pure scaling math
- `calculateSubtitlePosition(metrics, scale, alignment)` — ASS \an tag interpretation + positioning
- `applyPlatformFontCompensation(style, platform)` — macOS kerning/size adjustments
- `applySubtitleStyle(element, computedStyle)` — DOM style application
This can be done independently of or as part of TASK-6 (renderer split).
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 No single function exceeds ~50 lines in the positioning logic
- [ ] #2 Helper functions are pure where possible (take inputs, return outputs)
- [ ] #3 Platform-specific branches isolated into dedicated helpers
- [ ] #4 Invisible overlay positioning still works correctly on Linux and macOS
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Reparented as a dependency of TASK-27.5: the mega-function lives in positioning.ts (513 LOC), which is the exact file TASK-27.5 targets for splitting. Decomposing this function is a natural part of that file split. Should be executed together with TASK-27.5.
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,48 @@
---
id: TASK-12
title: Add renderer module bundling for multi-file renderer support
status: To Do
assignee: []
created_date: '2026-02-11 08:21'
updated_date: '2026-02-14 00:44'
labels:
- infrastructure
- renderer
- build
milestone: Codebase Clarity & Composability
dependencies:
- TASK-5
references:
- src/renderer/renderer.ts
- src/renderer/index.html
- package.json
- tsconfig.json
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Currently renderer.ts is a single file loaded directly by Electron's renderer process via a script tag in index.html. To split it into modules (TASK-6), we need a bundling step since Electron renderer's default context doesn't support bare ES module imports without additional configuration.
Options:
1. **esbuild** — fast, minimal config, already used in many Electron projects
2. **Electron's native ESM support** — requires `"type": "module"` and sandbox configuration
3. **TypeScript compiler output** — if targeting a single concatenated bundle
The build pipeline already compiles TypeScript and copies renderer assets. Adding a bundling step for the renderer would slot into the existing `npm run build` script.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Renderer code can be split across multiple .ts files with imports
- [ ] #2 Build pipeline bundles renderer modules into a single output for Electron
- [ ] #3 Existing `make build` still works end-to-end
- [ ] #4 No runtime errors in renderer process
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Priority promoted from medium to high: this unblocks clean multi-file renderer work and is a prerequisite for upcoming UI features (TASK-26 help modal, TASK-34 episode browser, and any future modal/overlay features).
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,45 @@
---
id: TASK-13
title: Fix macOS native window bounds for overlay binding
status: Done
assignee:
- codex
created_date: '2026-02-11 15:45'
updated_date: '2026-02-11 16:36'
labels:
- bug
- macos
- overlay
dependencies: []
references:
- src/window-trackers/macos-tracker.ts
- scripts/get-mpv-window-macos.swift
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Overlay windows on macOS are not properly aligned to the mpv window after switching from AppleScript window discovery to native Swift/CoreGraphics bounds retrieval.
Implement a robust native bounds strategy that prefers Accessibility window geometry (matching app-window coordinates used previously) and falls back to filtered CoreGraphics windows when Accessibility data is unavailable.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Overlay bounds track the active mpv window with correct position and size on macOS.
- [x] #2 Helper avoids selecting off-screen/non-primary mpv-related windows.
- [x] #3 Build succeeds with the updated macOS helper.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Follow-up in progress after packaged app runtime showed fullscreen fallback behavior:
- Added packaged-app helper path resolution in tracker (`process.resourcesPath/scripts/get-mpv-window-macos`).
- Added `.asar` helper materialization to temp path so child process execution is possible if candidate path resolves inside asar.
- Added throttled tracker logging for helper execution failures to expose runtime errors without log spam.
- Updated Electron builder `extraResources` to ship `dist/scripts/get-mpv-window-macos` outside asar at `resources/scripts/get-mpv-window-macos`.
- Added macOS-only invisible subtitle vertical nudge (`+5px`) in renderer layout to align interactive subtitles with mpv glyph baseline after bounds fix.
- Increased macOS-only invisible subtitle line-height for multi-line text to improve separation as line count grows.
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,51 @@
---
id: TASK-13
title: Fix second-instance --start when texthooker-only instance is running
status: Done
assignee: []
created_date: '2026-02-11 23:47'
updated_date: '2026-02-11 23:47'
labels:
- bugfix
- cli
- overlay
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
When SubMiner is already running in texthooker-only mode, a subsequent `--start` command from a second instance is currently ignored. This can leave users without an initialized overlay runtime even though startup commands were issued. Adjust CLI command handling so `--start` on second-instance initializes overlay runtime when it is not yet initialized, while preserving current ignore behavior when overlay runtime is already active.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Second-instance `--start` initializes overlay runtime when current instance has deferred/not-initialized overlay runtime.
- [x] #2 Second-instance `--start` remains ignored (existing behavior) when overlay runtime is already initialized.
- [x] #3 CLI command service tests cover both behaviors and pass.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Patched CLI second-instance `--start` handling in `src/core/services/cli-command-service.ts` to initialize overlay runtime when deferred.
Added regression test for deferred-runtime start path and updated initialized-runtime second-instance tests in `src/core/services/cli-command-service.test.ts`.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed overlay startup regression path where a second-instance `--start` could be ignored even when the primary instance was running in texthooker-only/deferred overlay mode.
Changes:
- Updated `handleCliCommandService` logic so `ignoreStart` applies only when source is second-instance, `--start` is present, and overlay runtime is already initialized.
- Added explicit overlay-runtime initialization path for second-instance `--start` when runtime is not initialized.
- Kept existing behavior for already-initialized runtime (still logs and ignores redundant `--start`).
- Added and updated tests in `cli-command-service.test.ts` to validate both deferred and initialized second-instance startup behaviors.
Validation:
- `pnpm run build` succeeded.
- `node dist/core/services/cli-command-service.test.js` passed (11/11).
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,43 @@
---
id: TASK-14
title: Ensure subminer launcher shows visible overlay on startup
status: Done
assignee: []
created_date: '2026-02-12 00:22'
updated_date: '2026-02-12 00:23'
labels:
- bugfix
- launcher
- overlay
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
The `subminer` launcher starts SubMiner with `--start` but can leave the visible overlay hidden when runtime config defers auto-show (`auto_start_overlay=false`). Update launcher command args to explicitly request visible overlay at startup so script-mode behavior matches user expectations.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Running `subminer <video>` starts SubMiner with startup args that include visible-overlay show intent.
- [x] #2 Launcher startup remains compatible with texthooker-enabled startup and backend/socket args.
- [x] #3 No regressions in existing startup argument construction for texthooker-only mode.
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Updated `subminer` launcher startup args in `startOverlay()` to include `--show-visible-overlay` alongside `--start`.
This makes script-mode startup idempotently request visible overlay presentation instead of depending on runtime config auto-start visibility flags, while preserving existing backend/socket and optional texthooker args.
Scope:
- `subminer` script only.
- No changes to AppImage internal CLI parsing or runtime services.
Validation:
- Verified argument block in `startOverlay()` now includes `--show-visible-overlay` and preserves existing flags.
- Confirmed texthooker-only path (`launchTexthookerOnly`) is unchanged.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,34 @@
---
id: TASK-15
title: Fix renderer module loading regression after task 6 split
status: Done
assignee: []
created_date: '2026-02-12 00:45'
updated_date: '2026-02-12 00:46'
labels:
- regression
- overlay
- renderer
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Overlay renderer stopped initializing after renderer.ts was split into modules. The emitted JS now uses CommonJS require/exports in a browser context (nodeIntegration disabled), causing script load failure and a blank transparent overlay with missing subtitle interactions.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Renderer script loads successfully in overlay BrowserWindow without nodeIntegration.
- [x] #2 Visible overlay displays subtitles again on initial launch.
- [x] #3 Overlay keyboard/mouse interactions are functional again.
- [x] #4 Build output remains compatible with Electron main/preload while renderer runs as browser modules.
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed a renderer module-loading regression introduced by renderer modularization. Added a dedicated renderer TypeScript build target (`tsconfig.renderer.json`) that emits browser-compatible ES modules, updated build script to compile renderer with that config, switched overlay HTML to load `renderer.js` as a module, and updated renderer runtime imports to `.js` module specifiers. Verified that built renderer output no longer contains CommonJS `require(...)` and that core test suite passes (`pnpm run test:core`).
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,34 @@
---
id: TASK-16
title: Revert overlay startup experiment changes and keep renderer fix
status: Done
assignee: []
created_date: '2026-02-12 01:45'
updated_date: '2026-02-12 01:46'
labels:
- regression
- overlay
- launcher
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
User confirmed renderer module-loading fix resolved the broken overlay, but startup experiment changes introduced side effects (e.g., y-s start path re-launch behavior). Revert non-essential auto-start/debugging changes in launcher/plugin/CLI startup flow while preserving renderer ESM fix.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Remove wrapper/plugin auto-start experiment changes that were added during debugging.
- [x] #2 Restore previous y-s start behavior without relaunching a new overlay session from wrapper-managed startup side effects.
- [x] #3 Keep renderer ESM/module-loading fix intact.
- [x] #4 Build and core tests pass after reversion.
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Reverted startup experiment changes while preserving the renderer ESM fix. Removed wrapper-forced visible overlay startup and wrapper-managed mpv script opts from `subminer`, restored plugin defaults/behavior (`auto_start=true`) and removed `wrapper_managed` handling from `plugin/subminer.lua` + `plugin/subminer.conf`, and reverted CLI/bootstrap debug-path changes in `src/core/services/cli-command-service.ts` and `src/core/services/startup-service.ts` with matching test updates. Verified `pnpm run build` and full `pnpm run test:core` pass.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,82 @@
---
id: TASK-17
title: Investigate dynamic overlay window sizing and decoupled secondary subtitle bar
status: Done
assignee:
- codex
created_date: '2026-02-12 02:27'
updated_date: '2026-02-12 09:42'
labels:
- overlay
- ux
- investigation
dependencies: []
documentation:
- overlay_window.md
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Current visible and invisible overlays can both be enabled at once, but both windows occupy full mpv bounds so hover/input targets conflict. Investigate feasibility of sizing each overlay window to subtitle content bounds (including secondary subtitles) instead of fullscreen. Also investigate decoupling secondary subtitle rendering from overlay visibility by introducing a dedicated top bar region (full-width or text-width) that remains at top and respects secondary subtitle display mode config (hover/always/never).
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Document current overlap/input conflict behavior when both overlays are enabled.
- [x] #2 Prototype or design approach for content-bounded overlay window sizing for visible and invisible overlays.
- [x] #3 Evaluate interaction model and technical constraints for a dedicated top secondary-subtitle bar independent from overlay visibility.
- [x] #4 Define implementation plan with tradeoffs, edge cases (line wrapping, long lines, resize, multi-monitor, mpv style sync), and recommended path forward.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Baseline current behavior
- Trace current visible/invisible overlay window creation and sizing behavior in main process and renderer paths.
- Document why enabling both overlays causes overlap and input/hover conflict.
2. Design content-bounded overlay sizing
- Evaluate measurement sources (renderer DOM bounds vs main process approximations).
- Define sizing algorithm (content bounds + padding + min/max + screen clamping) and update triggers.
- Identify required IPC contracts and ownership boundaries.
3. Design decoupled top secondary-subtitle bar
- Define a dedicated top region/window independent from primary subtitle visibility.
- Specify behavior for secondary subtitle display modes (hover/always/never) and pointer-event policy.
4. Recommend phased implementation
- Produce concrete file-level rollout steps, risks, edge cases (wrapping, long lines, resize, multi-monitor, style sync), and validation approach.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Completed code-path investigation across overlay window creation/sizing, visibility gating, IPC mouse passthrough, and renderer interaction layers.
Documented current conflict mechanism and architecture limitation: shared fullscreen bounds ownership across both overlay windows blocks safe simultaneous activation.
Proposed renderer-driven content-bounds IPC model, per-window bounds ownership, and a dedicated secondary top-bar window with mode-specific behavior.
Included phased implementation plan with risks and edge-case handling in docs/overlay-window-sizing-investigation.md.
Updated documentation reference to `overlay_window.md` because previous `docs/overlay-window-sizing-investigation.md` path is not present in repository.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Completed investigation and design for dynamic overlay window sizing and decoupled secondary subtitle rendering.
What changed:
- Added `docs/overlay-window-sizing-investigation.md` with a code-referenced analysis of current overlay behavior.
- Documented why overlap/input conflicts emerge under simultaneous overlay activation: both windows are full-size and currently share bounds ownership semantics.
- Designed a content-bounded sizing approach based on renderer-measured DOM bounds reported to main via IPC, with clamping, jitter guards, and fallback behavior.
- Evaluated top-bar decoupling options and recommended a dedicated secondary overlay window for independent lifecycle, z-order, and pointer policy.
- Defined a phased file-level implementation path and explicit edge-case handling (wrapping/long lines, resize churn, multi-monitor transitions, mpv style/render sync).
Validation:
- Investigation validated by tracing current runtime/services/renderer code paths; no runtime behavior changes were applied in this task.
- No automated tests were run because this task produced design documentation only (no executable code changes).
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,41 @@
---
id: TASK-18
title: Add remote mac mini build script
status: Done
assignee:
- codex
created_date: '2026-02-11 16:48'
updated_date: '2026-02-11 16:48'
labels:
- build
- macos
- devex
dependencies: []
references:
- scripts/build-external.sh
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add a script that offloads macOS package builds to a remote Mac mini over SSH, then syncs release artifacts back to the local workspace.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Script supports remote host and path configuration.
- [x] #2 Script supports signed and unsigned macOS build modes.
- [x] #3 Script syncs project sources to remote and copies release artifacts back locally.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Added `scripts/build-external.sh` with:
- Defaults for host alias (`mac-mini`) and remote path (`~/build/SubMiner`)
- Argument flags: `--host`, `--remote-path`, `--signed`, `--unsigned`, `--skip-sync`, `--sync-only`
- Rsync-based upload excluding heavyweight build artifacts and local dependencies
- Remote build execution (`pnpm run build:mac` or `pnpm run build:mac:unsigned`)
- Artifact sync back into local `release/`
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,53 @@
---
id: TASK-2
title: Post-refactor follow-ups from investigation.md
status: Done
assignee:
- codex
created_date: '2026-02-10 18:56'
updated_date: '2026-02-11 03:35'
labels: []
dependencies:
- TASK-1
references:
- investigation.md
- docs/refactor-main-checklist.md
ordinal: 13000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Execute the remaining follow-up work identified in investigation.md: remove unused scaffolding, add tests for high-risk consolidated services, and run manual smoke validation in a desktop MPV session.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Follow-up subtasks are created with explicit scope and dependencies.
- [x] #2 Unused architectural scaffolding and abandoned IPC abstraction files are removed or explicitly retained with documented rationale.
- [x] #3 Dedicated tests are added for higher-risk consolidated services (`overlay-shortcut-handler`, `mining-service`, `anki-jimaku-service`).
- [x] #4 Manual smoke checks for overlay rendering, mining flow, and field-grouping interaction are executed and results documented.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Create scoped subtasks for each recommendation in investigation.md and sequence them by risk and execution constraints.
2. Remove dead scaffolding files and any now-unneeded exports/imports; verify build/tests remain green.
3. Add focused behavior tests for the three higher-risk consolidated services.
4. Run and document desktop smoke validation in an MPV-enabled environment.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Completed:
- Created TASK-2.1 through TASK-2.5 from `investigation.md` recommendations.
- Finished TASK-2.1: removed unused scaffolding in `src/core/`, `src/modules/`, `src/ipc/` and cleaned internal-only service barrel export.
- Finished TASK-2.2: added dedicated tests for `overlay-shortcut-handler.ts`.
- Finished TASK-2.3: added dedicated tests for `mining-service.ts`.
- Finished TASK-2.4: added dedicated tests for `anki-jimaku-service.ts`.
Remaining:
- TASK-2.5: desktop smoke validation with MPV session
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,59 @@
---
id: TASK-2.1
title: Remove unused scaffolding and clean exports
status: Done
assignee:
- codex
created_date: '2026-02-10 18:56'
updated_date: '2026-02-11 03:35'
labels: []
dependencies:
- TASK-1
references:
- investigation.md
- src/core/action-bus.ts
- src/core/actions.ts
- src/core/app-context.ts
- src/core/module-registry.ts
- src/core/module.ts
- src/modules/
- src/ipc/
- src/core/services/index.ts
parent_task_id: TASK-2
ordinal: 10000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Remove unused module-architecture scaffolding and IPC abstraction files identified as dead code, and clean service barrel exports that are not needed outside service internals.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Files under `src/core/{action-bus.ts,actions.ts,app-context.ts,module-registry.ts,module.ts}` are removed if unreferenced.
- [x] #2 Unused `src/modules/` and `src/ipc/` scaffolding files are removed if unreferenced.
- [x] #3 `src/core/services/index.ts` no longer exports symbols that are only consumed internally (`isGlobalShortcutRegisteredSafe`).
- [x] #4 Build and core tests pass after cleanup.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Verify all candidate files are truly unreferenced in runtime/test paths.
2. Delete dead scaffolding files and folders.
3. Remove unnecessary service barrel exports and fix any import fallout.
4. Run `pnpm run build` and `pnpm run test:core`.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Removed unused scaffolding files from `src/core/`, `src/modules/`, and `src/ipc/` that were unreferenced by runtime code.
Updated `src/core/services/index.ts` to stop re-exporting `isGlobalShortcutRegisteredSafe`, which is only used internally by service files.
Verification:
- `pnpm run build` passed
- `pnpm run test:core` passed (18/18)
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,46 @@
---
id: TASK-2.2
title: Add tests for overlay shortcut handler service
status: Done
assignee:
- codex
created_date: '2026-02-10 18:56'
updated_date: '2026-02-11 03:35'
labels: []
dependencies:
- TASK-2.1
references:
- investigation.md
- src/core/services/overlay-shortcut-handler.ts
parent_task_id: TASK-2
ordinal: 8000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add dedicated tests for `overlay-shortcut-handler.ts`, covering shortcut runtime handlers, fallback behavior, and key edge/error paths.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Shortcut registration/unregistration handler behavior is covered.
- [x] #2 Fallback handling paths are covered for valid and invalid input.
- [x] #3 Error and guard behavior is covered for missing dependencies/state.
- [x] #4 `pnpm run test:core` remains green.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Added `src/core/services/overlay-shortcut-handler.test.ts` with coverage for:
- runtime handler dispatch for sync and async actions
- async error propagation to OSD/log handling
- local fallback action matching, including timeout forwarding
- `allowWhenRegistered` behavior for secondary subtitle toggle
- no-match fallback return behavior
Updated `package.json` `test:core` to include `dist/core/services/overlay-shortcut-handler.test.js`.
Verification: `pnpm run test:core` passed (19/19 at completion of this ticket).
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,45 @@
---
id: TASK-2.3
title: Add tests for mining service
status: Done
assignee:
- codex
created_date: '2026-02-10 18:56'
updated_date: '2026-02-11 03:35'
labels: []
dependencies:
- TASK-2.1
references:
- investigation.md
- src/core/services/mining-service.ts
parent_task_id: TASK-2
ordinal: 7000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add dedicated behavior tests for `mining-service.ts` covering sentence/card mining orchestration and error boundaries.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Happy-path behavior is covered for mining entry points.
- [x] #2 Guard/early-return behavior is covered for missing runtime state.
- [x] #3 Error paths are covered with expected logging/OSD behavior.
- [x] #4 `pnpm run test:core` remains green.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Added `src/core/services/mining-service.test.ts` with focused coverage for:
- `copyCurrentSubtitleService` guard and success behavior
- `mineSentenceCardService` integration/connection guards and success path
- `handleMultiCopyDigitService` history-copy behavior with truncation messaging
- `handleMineSentenceDigitService` async error catch and OSD/log propagation
Updated `package.json` `test:core` to include `dist/core/services/mining-service.test.js`.
Verification: `pnpm run test:core` passed (20/20 after adding mining tests).
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,49 @@
---
id: TASK-2.4
title: Add tests for anki jimaku service
status: Done
assignee:
- codex
created_date: '2026-02-10 18:56'
updated_date: '2026-02-11 03:35'
labels: []
dependencies:
- TASK-2.1
references:
- investigation.md
- src/core/services/anki-jimaku-service.ts
parent_task_id: TASK-2
ordinal: 6000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add dedicated tests for `anki-jimaku-service.ts` focusing on IPC handler registration, request dispatch, and error handling behavior.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 IPC registration behavior is validated for all channels exposed by this service.
- [x] #2 Success-path behavior for core handler flows is validated.
- [x] #3 Failure-path behavior is validated with expected error propagation.
- [x] #4 `pnpm run test:core` remains green.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Added a lightweight registration-injection seam to `registerAnkiJimakuIpcRuntimeService` so runtime behavior can be tested without Electron IPC globals.
Added `src/core/services/anki-jimaku-service.test.ts` with coverage for:
- runtime handler surface registration
- integration disable path and runtime-options broadcast
- subtitle history clear and field-grouping response callbacks
- merge-preview guard error and integration success delegation
- Jimaku search request mapping/result capping
- downloaded-subtitle MPV command forwarding
Updated `package.json` `test:core` to include `dist/core/services/anki-jimaku-service.test.js`.
Verification: `pnpm run test:core` passed (21/21).
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,58 @@
---
id: TASK-2.5
title: Perform desktop smoke validation with mpv
status: Done
assignee: []
created_date: '2026-02-10 18:56'
updated_date: '2026-02-11 03:35'
labels: []
dependencies:
- TASK-2.2
- TASK-2.3
- TASK-2.4
references:
- investigation.md
- docs/refactor-main-checklist.md
parent_task_id: TASK-2
ordinal: 12000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Execute manual desktop smoke checks in an MPV-enabled environment to validate overlay rendering and key user workflows not fully covered by automated tests.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Overlay rendering and visibility toggling are verified in a real desktop session.
- [x] #2 Card mining flow is verified end-to-end.
- [x] #3 Field-grouping interaction is verified end-to-end.
- [x] #4 Results and any follow-up defects are documented in task notes.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Smoke run executed on 2026-02-10 with real Electron launch (outside sandbox) after unsetting `ELECTRON_RUN_AS_NODE=1` in command context.
Commands executed:
- `electron . --help`
- `electron . --start`
- `electron . --toggle-visible-overlay`
- `electron . --toggle-invisible-overlay`
- `electron . --mine-sentence`
- `electron . --trigger-field-grouping`
- `electron . --open-runtime-options`
- `electron . --stop`
Observed runtime evidence from app logs:
- CLI help output rendered with expected flags.
- App started and connected to MPV after reconnect attempts.
- Mining flow executed and produced `Created sentence card: ...`, plus media upload logs.
- Tracker/runtime loop started (`hyprland` tracker connected) and app stopped cleanly.
Follow-up/constraints:
- Overlay *visual rendering* and visibility correctness are not directly observable from terminal logs alone and still require direct desktop visual confirmation.
- Field-grouping trigger command was sent, but explicit end-state confirmation in UI still needs manual verification.
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,42 @@
---
id: TASK-20.2
title: Add renderer-to-main IPC contract for measured overlay content bounds
status: Done
assignee: []
created_date: '2026-02-12 08:47'
updated_date: '2026-02-13 08:05'
labels: []
dependencies: []
parent_task_id: TASK-20
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add renderer-to-main IPC for content measurement reporting, so main process can size each overlay window from post-layout DOM bounds.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Preload exposes a typed API for reporting overlay content bounds with layer metadata.
- [x] #2 Main-process IPC handler validates payload shape/range and stores latest measurement per layer.
- [x] #3 Renderer emits measurement updates on subtitle, mode, style, and render-metric changes with throttling/debounce.
- [x] #4 No crashes or unbounded logging when measurements are missing/empty/invalid; fallback behavior is explicit.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Added a typed `OverlayContentMeasurement` IPC contract exposed in preload and Electron API typings. Implemented a main-process measurement store with strict payload validation and rate-limited warning logs for invalid reports. Added renderer-side debounced measurement reporting that emits updates on subtitle content/mode/style/render-metric and resize changes, explicitly sending `contentRect: null` when no measured content exists to signal fallback behavior.
Added unit coverage for measurement validation and store behavior.
Closed per user request to delete parent task and subtasks.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Implemented renderer-to-main measurement reporting for overlay content bounds with per-layer metadata. Main now validates and stores latest measurements per layer safely, renderer emits debounced updates on relevant state changes, and invalid/missing payload handling is explicit and non-spammy.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,38 @@
---
id: TASK-21
title: Persist and restore MPV secondary subtitle visibility across app lifecycle
status: Done
assignee: []
created_date: '2026-02-13 07:59'
updated_date: '2026-02-13 08:01'
labels: []
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
When SubMiner connects to MPV, capture the current MPV `secondary-sub-visibility` value and force it off. Keep it off during SubMiner runtime regardless of overlay visibility toggles. On app shutdown (and MPV shutdown event when possible), restore MPV `secondary-sub-visibility` to the captured pre-SubMiner value.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Capture MPV `secondary-sub-visibility` once per MPV connection before overriding it.
- [x] #2 Set MPV `secondary-sub-visibility` to `no` after capture regardless of `bind_visible_overlay_to_mpv_sub_visibility`.
- [x] #3 Do not mutate/restore secondary MPV visibility as a side effect of visible overlay toggles.
- [x] #4 Restore captured secondary MPV visibility on app shutdown while MPV is connected.
- [x] #5 Attempt restore on MPV shutdown event before disconnect and clear captured state afterward.
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Implemented MPV secondary subtitle visibility lifecycle management:
- Moved secondary-sub visibility capture/disable to MPV connection initialization (`getInitialState` requests `secondary-sub-visibility`, then request handler stores prior value and forces `secondary-sub-visibility=no`).
- Removed secondary-sub visibility side effects from visible overlay visibility service so overlay toggles no longer capture/restore secondary MPV state.
- Added `restorePreviousSecondarySubVisibility()` to `MpvIpcClient`, invoked on MPV `shutdown` event and from app `onWillQuitCleanup` (best effort while connected).
- Wired new dependency getter/setter in main runtime bootstrap for tracked previous secondary visibility state.
- Added unit coverage in `mpv-service.test.ts` for capture/disable and restore/clear behavior.
- Verified with `pnpm run build` and `node --test dist/core/services/mpv-service.test.js`.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,30 @@
---
id: TASK-22
title: Make secondary subtitles hover-revealed but non-lookupable in Yomitan sessions
status: To Do
assignee: []
created_date: '2026-02-13 16:40'
labels: []
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Investigate and implement a UX where secondary subtitles (e.g., English text in our current sessions) become visible when hovered, while explicitly preventing user interactions that allow text lookup (including Yomitan integration) on those subtitles. This should allow readability-on-hover without exposing the secondary overlay text to selection/lookup workflows.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Secondary subtitles become visible only while hovered (or via equivalent hover-triggered mechanism), and return to default hidden/low-visibility state when not hovered.
- [ ] #2 When hovered, secondary subtitles do not trigger Yomitan lookup behavior in sessions where Yomitan is enabled.
- [ ] #3 Secondary subtitles remain non-interactive for lookup paths (for example, text selection or lookup event propagation) while hover-visibility still works as intended.
- [ ] #4 Primary subtitles remain functional and are not regressed by the secondary-subtitle interaction changes.
- [ ] #5 If complete prevention of Yomitan lookup on secondary subtitles is not technically possible, the task includes the known limitations and a documented fallback behavior.
<!-- AC:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Acceptance criteria are reviewed and covered by explicit manual/automated test coverage for hover reveal and lookup suppression behavior.
<!-- DOD:END -->

View File

@@ -0,0 +1,38 @@
---
id: TASK-23
title: >-
Add opt-in JLPT level tagging by bundling and querying local Yomitan
dictionary
status: To Do
assignee: []
created_date: '2026-02-13 16:42'
labels: []
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Implement an opt-in JLPT token annotation feature that annotates subtitle words with JLPT level in-session. The feature should use a bundled dictionary source from the existing JLPT Yomitan extension, parse/query its dictionary file to determine whether a token appears and its JLPT level, and render token-level visual tags as a colored underline spanning each token length. Colors must correspond to JLPT levels (e.g., N5/N4/N3/N2/N1) using a consistent mapping.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Add an opt-in setting/feature flag so JLPT tagging is disabled by default and can be enabled per user/session as requested.
- [ ] #2 Bundle the existing JLPT Yomitan extension package/data into the project so lookups can be performed offline from local files.
- [ ] #3 Implement token-level dictionary lookup against the bundled JLPT dictionary file to determine presence and JLPT level for words in subtitle lines.
- [ ] #4 Render a colored underline under each token determined to have a JLPT level; the underline must match token width/length and not affect layout or disrupt line rendering.
- [ ] #5 Assign different underline colors per JLPT level (at minimum N5/N4/N3/N2/N1) with a stable mapping documented in task notes.
- [ ] #6 Handle unknown/no-match tokens as non-tagged while preserving existing subtitle styling and interaction behavior.
- [ ] #7 When disabled, no JLPT lookups are performed and subtitles render exactly as current behavior.
- [ ] #8 Add tests or deterministic checks covering at least one positive match, one non-match, and one unknown/unsupported-level fallback path.
- [ ] #9 Document expected dictionary source and any size/performance impact of bundling the JLPT extension data.
- [ ] #10 If dictionary format/version constraints block exact level extraction, the task includes explicit limitation notes and a deterministic fallback strategy.
<!-- AC:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Feature has a clear toggle and persistence of preference if applicable.
- [ ] #2 JLPT rendering is visually verified for all supported levels with distinct colors and no overlap/regression in subtitle legibility.
<!-- DOD:END -->

View File

@@ -0,0 +1,31 @@
---
id: TASK-23.1
title: Implement JLPT token lookup service for subtitle words
status: To Do
assignee: []
created_date: '2026-02-13 16:42'
labels: []
dependencies: []
parent_task_id: TASK-23
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Create a lookup layer that parses/queries the bundled JLPT dictionary file and returns JLPT level for a given token/word. Integrate with subtitle tokenization path with minimal performance overhead.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Service accepts a token/normalized token and returns JLPT level or no-match deterministically.
- [ ] #2 Lookup handles expected dictionary format edge cases and unknown tokens without throwing.
- [ ] #3 Lookup path is efficient enough for frame-by-frame subtitle updates.
- [ ] #4 Tokenizer interaction preserves existing token ordering and positions needed for rendering spans/underlines.
- [ ] #5 Behavior on malformed/unsupported dictionary format is documented with fallback semantics.
<!-- AC:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Lookup service returns JLPT level with deterministic output for test fixtures.
<!-- DOD:END -->

View File

@@ -0,0 +1,30 @@
---
id: TASK-23.2
title: Bundle JLPT Yomitan dictionary assets for offline local lookup
status: To Do
assignee: []
created_date: '2026-02-13 16:42'
labels: []
dependencies: []
parent_task_id: TASK-23
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Package and include the JLPT Yomitan extension dictionary assets in SubMiner so JLPT tagging can run without external network calls. Define a deterministic build/runtime path and loading strategy for the dictionary file and metadata versioning.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 JLPT dictionary asset from the existing Yomitan extension is added to the repository/build output in a tracked, offline-available location.
- [ ] #2 The loader locates and opens the JLPT dictionary file deterministically at runtime.
- [ ] #3 Dictionary version/source is documented so future updates are explicit and reproducible.
- [ ] #4 Dictionary bundle size and load impact are documented in task notes or project docs.
<!-- AC:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Dictionary data is bundled and consumable during development and packaged app runs.
<!-- DOD:END -->

View File

@@ -0,0 +1,31 @@
---
id: TASK-23.3
title: Render JLPT token underlines with level-based colors in subtitle lines
status: To Do
assignee: []
created_date: '2026-02-13 16:42'
labels: []
dependencies: []
parent_task_id: TASK-23
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Render JLPT-aware token annotations as token-length colored underlines in the subtitle UI based on returned JLPT levels, without changing existing subtitle layout or primary interaction behavior.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 For each token with JLPT level, renderer draws an underline matching token width/length.
- [ ] #2 Underlines use distinct colors by JLPT level (e.g., N5/N4/N3/N2/N1) and mapping is consistent/documented.
- [ ] #3 Non-tagged tokens remain visually unchanged.
- [ ] #4 Rendering does not alter line height/selection behavior or break wrapping behavior.
- [ ] #5 Feature degrades gracefully when level data is missing or lookup is unavailable.
<!-- AC:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Visual output validated for all mapped JLPT levels with no legibility/layout regressions.
<!-- DOD:END -->

View File

@@ -0,0 +1,30 @@
---
id: TASK-23.4
title: Add opt-in control and end-to-end flow + tests for JLPT tagging
status: To Do
assignee: []
created_date: '2026-02-13 16:42'
labels: []
dependencies: []
parent_task_id: TASK-23
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add user/config setting to enable JLPT tagging, wire the feature toggle through subtitle processing/rendering, and add tests/verification for positive match, non-match, and disabled-mode behavior.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 JLPT tagging is opt-in and defaults to disabled.
- [ ] #2 When disabled, lookup/rendering pipeline does not execute JLPT processing.
- [ ] #3 When enabled, end-to-end flow tags subtitle words via token-level lookup and rendering.
- [ ] #4 Add tests covering at least one positive match, one non-match, and disabled state.
<!-- AC:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 End-to-end option behavior and opt-in state persistence are implemented and verified.
<!-- DOD:END -->

View File

@@ -0,0 +1,44 @@
---
id: TASK-24
title: >-
Add N+1 word highlighting using Anki-known-word cache with initial sync and
periodic refresh
status: Done
assignee: []
created_date: '2026-02-13 16:45'
updated_date: '2026-02-15 08:17'
labels: []
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Implement subtitle highlighting for words already known in Anki (N+1 workflow support) by introducing a one-time bootstrap query of the users Anki known-word set, storing it locally, and refreshing it periodically to reflect deck updates. The feature should allow fast in-session lookups to determine known words and visually distinguish them in subtitle rendering.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Add an opt-in setting/feature flag for N+1 highlighting and default it to disabled for backward-compatible behavior.
- [x] #2 Implement a one-time import/sync that queries known-word data from Anki into a local store on first enable or explicit refresh.
- [x] #3 Store known words locally in an efficient structure for fast lookup during subtitle rendering.
- [x] #4 Run periodic refresh on a configurable interval and expose a manual refresh action.
- [x] #5 Ensure local cache updates replace or merge safely without corrupting in-flight subtitle rendering queries.
- [x] #6 Known/unknown lookup decisions are applied consistently to subtitle tokens for highlighting without impacting tokenization performance.
- [x] #7 Non-targeted words remain visually unchanged and all existing subtitle interactions remain unaffected.
- [x] #8 Add tests/validation for initial sync success, refresh update, and disabled-mode no-lookup behavior.
- [x] #9 Document Anki data source expectations, failure handling, and update policy/interval behavior.
- [x] #10 If full Anki query integration is not possible in this environment, define deterministic fallback behavior with clear user-visible messaging.
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Implemented in refactor via merge from task-24-known-word-refresh (commits 854b8fb, e8f2431, ed5a249). Includes manual/periodic known-word cache refresh, opt-in N+1 highlighting path, cache persistence behavior, CLI refresh command, and related tests/docs updates.
<!-- SECTION:FINAL_SUMMARY:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [x] #1 N+1 known-word highlighting is configurable, performs local cached lookups, and is demonstrated to update correctly after periodic/manual refresh.
<!-- DOD:END -->

View File

@@ -0,0 +1,38 @@
---
id: TASK-25
title: >-
Add frequency-dictionary-based token highlighting with configurable top-X and
color ramp
status: To Do
assignee: []
created_date: '2026-02-13 16:47'
labels: []
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Leverage user-installed frequency dictionaries to color subtitle tokens based on word frequency rank, with configurable behavior: either one shared color for all words below a rank threshold or a multi-color range mapping based on frequency bands. The feature should support a configurable X (top-N words) cutoff and integrate with existing subtitle rendering flow.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Add a feature flag and configuration for frequency-based highlighting with default disabled state.
- [ ] #2 Support selecting a user-installed frequency dictionary source and reading word frequency data from it.
- [ ] #3 Introduce a configurable top-X threshold in config for which words are eligible for frequency-based coloring.
- [ ] #4 When single-color mode is enabled, all matched words within the rank rule use the configured color.
- [ ] #5 When multi-color mode is enabled, map frequency bands to colors and color tokens by their actual rank bucket.
- [ ] #6 Ensure matching is token-aware (normalization/lowercasing handling) and preserves existing subtitle tokenization behavior.
- [ ] #7 Handle missing/unsupported dictionary formats and unknown words with deterministic no-highlight fallback.
- [ ] #8 Render underline/token highlights without breaking subtitle layout or interactions.
- [ ] #9 Add tests/verification for: single-color mode, color-band mode, threshold boundary, and disabled mode.
- [ ] #10 Document dictionary source format expectations, configuration example, and performance impact of ranking lookups.
- [ ] #11 If full automatic discovery of user-installed frequency dictionaries is not possible, provide clear configuration workflow/fallback path.
<!-- AC:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Frequency-based highlighting renders using either single-color or banded-colors for valid matches, with configurable top-X threshold and documented setup.
<!-- DOD:END -->

View File

@@ -0,0 +1,37 @@
---
id: TASK-26
title: >-
Add session help modal with dynamic keybinding/color legend and keyboard/mouse
navigation
status: To Do
assignee: []
created_date: '2026-02-13 16:49'
labels: []
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Create a help modal that auto-generates its content from the project/app layout and current configuration, showing the active keybindings and color keys for the current session in a presentable way. The modal should be navigable with arrow keys and Vim-style hjkl-style movement behavior internally but without labeling hjkl in UI, and support mouse interaction. Escape closes/goes back. Open the modal with `y-h`, and if that binding is already taken, use `y-k` as fallback.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Help modal content is generated automatically from current keybinding config and project/app layout rather than hardcoded static text.
- [ ] #2 Modal displays current session keybindings and active color-key mappings in a clear, grouped layout with section separation for readability.
- [ ] #3 Modal can be opened with `y-h` when available; if `y-h` is already bound, modal opens with `y-k` instead.
- [ ] #4 Close behavior: `Escape` exits the modal and returns to previous focus/state.
- [ ] #5 Modal supports mouse-based interaction for standard focus/selection actions.
- [ ] #6 Navigation inside modal supports arrow keys for movement between focusable items.
- [ ] #7 Modal supports internal navigation semantics equivalent to hjkl directional movement for users, while UI text/labels do not mention hjkl keys.
- [ ] #8 No visible UI mention of Vim key names is shown in modal labels/help copy.
- [ ] #9 Modal opens from current session without requiring a restart and reflects updated config changes without code changes.
- [ ] #10 If the shortcut is unavailable due to conflicts, user-visible fallback behavior/error is deterministic and documented.
<!-- AC:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Auto-generated help modal displays up-to-date keybinding + color mapping data and supports both keyboard (arrow/fallback path) and mouse navigation with Escape-to-close.
<!-- DOD:END -->

View File

@@ -0,0 +1,86 @@
---
id: TASK-27
title: >-
Refactor project structure to reduce architectural complexity and split
oversized modules
status: In Progress
assignee: []
created_date: '2026-02-13 17:13'
updated_date: '2026-02-15 07:00'
labels:
- 'owner:architect'
- 'owner:backend'
- 'owner:frontend'
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Create a phased backlog-backed restructuring plan that keeps current service-oriented architecture while reducing cognitive load from oversized modules and tightening module ownership boundaries.
This initiative should make future feature work easier by splitting high-complexity files, reducing tightly-coupled orchestration, and introducing measurable structural guardrails.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 A phased decomposition plan is defined in task links and references the following target files: src/main.ts, src/anki-integration.ts, src/core/services/mpv-service.ts, src/renderer/*, src/config/*, and src/core/services/*.
- [ ] #2 Tasks are assigned with clear owners and include explicit dependencies so execution can proceed in parallel where safe.
- [ ] #3 Changes are constrained to structural refactors first (no behavior changes until foundational splits are in place).
- [ ] #4 Each subtask includes test/verification expectations (manual or automated) and a rollback-safe checkpoint.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
## Revised Execution Sequence
### Phase 0 — Prerequisites (outside TASK-27 tree)
- **TASK-7** — Extract main.ts global state into AppState container (required before TASK-27.2)
- **TASK-9** — Remove trivial wrapper functions from main.ts (depends on TASK-7; recommended before TASK-27.2 but not blocking)
### Phase 1 — Lightweight Inventory
- **TASK-27.1** — Inventory files >400 LOC, document contracts, define smoke test checklist
### Phase 2 — Sequential Split Wave
Order matters to avoid merge conflicts:
1. **TASK-27.3** — anki-integration.ts split (self-contained, doesn't affect main.ts wiring until facade is stable)
2. **TASK-27.2** — main.ts split (after TASK-7 provides AppState container and 27.3 stabilizes the Anki facade)
3. **TASK-27.4** — mpv-service.ts split (absorbs TASK-8 scope; blocked until 27.1 is done)
4. **TASK-27.5** — renderer positioning.ts split (downscoped; after 27.2 to avoid import-path conflicts)
### Phase 3 — Stabilization
- **TASK-27.6** — Quality gates and CI enforcement
## Smoke Test Checklist (applies to all subtasks)
Every subtask must verify before merging:
- [ ] App starts and connects to MPV
- [ ] Subtitle text appears in overlay
- [ ] Card mining creates a note in Anki
- [ ] Field grouping modal opens and resolves
- [ ] Global shortcuts work (mine, toggle overlay, copy subtitle)
- [ ] Secondary subtitle display works
- [ ] TypeScript compiles with no new errors
- [ ] All existing tests pass (`pnpm test:core && pnpm test:config`)
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
## Review Findings (2026-02-13)
### Key changes from original plan:
1. **Dropped parallel execution of Phase 2** — TASK-27.2 and 27.5 share import paths; 27.2 and 27.3 share main.ts wiring. Sequential order prevents merge conflicts.
2. **Added TASK-7 as external prerequisite** — main.ts has 30+ module-level `let` declarations. Splitting files without a state container first just scatters mutable state.
3. **TASK-8 absorbed into TASK-27.4** — TASK-8 (separate protocol from app logic) and TASK-27.4 (physical file split) overlap significantly. TASK-27.4 now covers both.
4. **TASK-27.5 downscoped** — Renderer is already well-organized (241-line orchestrator, handlers/, modals/, utils/ directories). Only positioning.ts (513 LOC) needs splitting.
5. **Simplified ownership model** — Removed multi-owner ceremony since this is effectively a solo project. Kept labels for categorical tracking only.
6. **Added global smoke test checklist** — No end-to-end or renderer tests exist, so manual verification is the safety net for every subtask.
<!-- SECTION:NOTES:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Plan task links and ordering are recorded in backlog descriptions.
- [ ] #2 At least 2 independent owners are assigned with explicit labels in subtasks.
<!-- DOD:END -->

View File

@@ -0,0 +1,48 @@
---
id: TASK-27.1
title: Map component ownership boundaries and migration path before file splitting
status: Done
assignee:
- backend
created_date: '2026-02-13 17:13'
updated_date: '2026-02-14 08:43'
labels:
- refactor
- documentation
dependencies: []
references:
- docs/architecture.md
documentation:
- docs/structure-roadmap.md
parent_task_id: TASK-27
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Create a lightweight inventory of files needing refactoring and their API contracts to prevent accidental coupling regression during splits.
This is a documentation-only task — no code changes. Its output (docs/structure-roadmap.md) is the prerequisite gate for all Phase 2 subtasks.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Inventory source files with >400 LOC and categorize by concern (bootstrap, Anki integration, MPV protocol, renderer, config, services).
- [ ] #2 Document exported API surface for each target file (entry points, exported types, event names, primary callers).
- [ ] #3 List the split sequence from the parent task plan and note any known risks per step.
- [ ] #4 Store results in docs/structure-roadmap.md and link from this task.
- [ ] #5 Include the global smoke test checklist from the parent TASK-27 plan.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
## Simplification Notes
Original task called for named maintainer owners per component, risk-gated migration sequences, and success metrics per slice. This is heavyweight for a solo project where the developer already knows the codebase intimately.
Reduced to: file inventory, API contracts, sequence + risks, and a shared smoke test checklist. The review analysis (in TASK-27 notes) already covers much of what this task would produce — this task captures it in a durable docs file.
Generated docs/structure-roadmap.md with file inventory, task-specific API contracts, split sequence, known risks, and smoke checklist to unlock TASK-27 subtasks.
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,99 @@
---
id: TASK-27.2
title: Split main.ts into composition-root modules
status: Done
assignee:
- backend
created_date: '2026-02-13 17:13'
updated_date: '2026-02-15 01:25'
labels:
- 'owner:backend'
- 'owner:architect'
dependencies:
- TASK-27.1
- TASK-7
references:
- src/main.ts
documentation:
- docs/architecture.md
parent_task_id: TASK-27
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Reduce main.ts complexity by extracting bootstrap, lifecycle, overlay, IPC, and CLI wiring into explicit modules while keeping runtime behavior unchanged.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Create modules under src/main/ for bootstrap/lifecycle/ipc/overlay/cli concerns.
- [x] #2 main.ts no longer owns session-specific business state; it only composes services and starts the app.
- [ ] #3 Public service behavior, startup order, and flags remain unchanged, validated by existing integration/manual smoke checks.
- [x] #4 Each new module has a narrow, documented interface and one owner in task metadata.
- [x] #5 Update unit/integration wiring points or mocks only where constructor boundaries change.
- [x] #6 Add a migration note in docs/structure-roadmap.md.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
## Dependency Context
**TASK-7 is a hard prerequisite.** main.ts currently has 30+ module-level `let` declarations (mpvClient, yomitanExt, reconnectTimer, currentSubText, subtitlePosition, keybindings, ankiIntegration, secondarySubMode, etc.). Splitting main.ts into src/main/ submodules without first consolidating this state into a typed AppState container would scatter mutable state across files, making data flow even harder to trace.
**TASK-9 (remove trivial wrappers)** depends on TASK-7 and should ideally complete before this task starts, since it reduces the surface area of main.ts. However, it's not a hard blocker — wrapper removal can happen during or after the split.
**Sequencing note:** This task should run AFTER TASK-27.3 (anki-integration split) completes, because AnkiIntegration is instantiated and heavily wired in main.ts. Changing both the composition root and the Anki facade simultaneously creates integration risk. Let 27.3 stabilize the Anki module boundaries first, then split main.ts around the stable API.
## Folded-in work from TASK-9 and TASK-10
TASK-9 (remove trivial wrappers) and TASK-10 (naming conventions) have been deprioritized to low. Their scope is largely subsumed by this task:
- When main.ts is split into composition-root modules, trivial wrappers will naturally be eliminated or inlined at each module boundary.
- Naming conventions should be standardized per-module as they are extracted, not as a separate global pass.
Refer to TASK-9 and TASK-10 acceptance criteria as checklists during execution of this task.
Deferred until TASK-7 validation and TASK-27.3 completion to avoid import-order/state scattering during composition-root extraction.
Started `TASK-27.2` with a small composition-root extraction in `src/main.ts`: extracted `createMpvClientRuntimeService()` from inline `createMpvClient` deps object to reduce bootstrap-local complexity and prepare for module split (`main.ts` still owns state and startup sequencing remains unchanged).
Added `createCliCommandRuntimeServiceDeps()` helper in `src/main.ts` and routed `handleCliCommand` through it, preserving existing `createCliCommandDepsRuntimeService` wiring while reducing inline dependency composition churn.
Refactored `handleMpvCommandFromIpc` to use `createMpvCommandRuntimeServiceDeps()` helper, removing inline dependency object and keeping command dispatch behavior unchanged.
Added `createAppLifecycleRuntimeDeps()` helper in `src/main.ts` and moved the full inline app-lifecycle dependency graph into it, so startup wiring now delegates to `createAppLifecycleDepsRuntimeService(createAppLifecycleRuntimeDeps())` and the composition root is further decoupled from lifecycle behavior.
Extracted startup bootstrap composition in `src/main.ts` by adding `createStartupBootstrapRuntimeDeps()`, replacing the inline `runStartupBootstrapRuntimeService({...})` object with a factory for parse/startup logging/config/bootstrap wiring.
Fixed TS strict errors introduced by factory extractions by adding explicit runtime dependency interface annotations to factory helpers (`createStartupBootstrapRuntimeDeps`, `createAppLifecycleRuntimeDeps`, `createCliCommandRuntimeServiceDeps`, `createMpvCommandRuntimeServiceDeps`, `createMainIpcRuntimeServiceDeps`, `createAnkiJimakuIpcRuntimeServiceDeps`) and by typing `jimakuFetchJson` wrapper generically to satisfy `AnkiJimakuIpcRuntimeOptions`.
Extracted app-ready startup dependency object into `createAppReadyRuntimeDeps(): AppReadyRuntimeDeps`, moving the inline `runAppReadyRuntimeService({...})` payload out of `createAppLifecycleRuntimeDeps()` while preserving behavior.
Added `SubsyncRuntimeDeps` typing to `getSubsyncRuntimeDeps()` for clearer composition-root contracts around subsync IPC/dependency wiring (`runSubsyncManualFromIpcRuntimeService`/`triggerSubsyncFromConfigRuntimeService` path).
Extracted additional composition-root dependency composition for IPC command handlers into src/main/dependencies.ts: createCliCommandRuntimeServiceDeps(...) and createMpvCommandRuntimeServiceDeps(...). main.ts now inlines stateful callbacks into these shared builders while preserving behavior. Next step should be extracting startup/app-ready/lifecycle/overlay wiring into dedicated modules under src/main/.
Progress update (2026-02-14): committed `bbfe2a9` (`refactor: extract overlay shortcuts runtime for task 27.2`). `src/main/overlay-shortcuts-runtime.ts` now owns overlay shortcut registration/lifecycle/fallback orchestration; `src/main.ts` and `src/main/cli-runtime.ts` now consume factory helpers with stricter typed async contracts. Build verified via `pnpm run build`.
Remaining for TASK-27.2: continue extracting remaining `main.ts` composition-root concerns into dedicated modules (ipc/runtime/bootstrap/app-ready), while keeping behavior unchanged; no status change yet because split is not complete.
Added `src/main/startup-lifecycle.ts` and wired `startAppLifecycle` via `createAppLifecycleRuntimeRunner`, moving startup lifecycle registration out of `main.ts` inline wiring. Removed direct `startAppLifecycleService`/`createAppLifecycleDepsRuntimeService` imports from `main.ts` because they are now encapsulated behind the new helper.
This is the final lifecycle composition chunk for TASK-27.2 before moving to optional app-ready split work. Build feedback from user has remained clean around this refactor area.
Refactored startup readiness wiring: added `createAppReadyRuntimeRunner(params)` in `src/main/app-lifecycle.ts` and switched `startAppLifecycle` construction in `main.ts` to use it. This removes direct `runAppReadyRuntimeService` usage from `main.ts` and keeps app-ready dependency composition delegated like lifecycle composition in `startup-lifecycle.ts`.
Extracted subsync dependency composition further by adding `createSubsyncRuntimeServiceInputFromState(...)` in `src/main/subsync-runtime.ts` and updating `main.ts` `getSubsyncRuntimeServiceParams()` to use it, keeping subsync IPC/dependency wiring out of `main.ts` stateful callsites.
TASK-27.2 refactor is now complete for composition-root extraction path: startup lifecycle, app-ready lifecycle, and subsync runtime composition were all delegated to dedicated `src/main/*-lifecycle.ts`, `app-lifecycle.ts`, and `subsync-runtime.ts` modules. `main.ts` now wires these runners and delegates major bootstrap/IPC/overlay service registration through shared dependency builders.
Updated `src/main/state.ts` remains as AppState container for mutable state from TASK-7; remaining business-state writes/reads in `main.ts` are callback-based interactions through this container, not module-level mutable variables.
Per build validation after each chunk, `pnpm build` has been passing.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Refactored IPC runtime registration in `main.ts` to pass `ankiJimaku` deps through `createAnkiJimakuIpcRuntimeServiceDeps(...)` and removed the bespoke `buildIpcRuntimeServicesParams()` helper; registration remains in `main.ts` via `registerIpcRuntimeServices({ ... })` with shared runtime service builders.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,73 @@
---
id: TASK-27.3
title: Refactor anki-integration.ts into domain-specific service modules
status: Done
assignee:
- backend
created_date: '2026-02-13 17:13'
updated_date: '2026-02-15 04:23'
labels:
- 'owner:backend'
dependencies:
- TASK-27.1
references:
- src/anki-integration.ts
documentation:
- docs/architecture.md
parent_task_id: TASK-27
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Split anki-integration.ts (2,679 LOC, 60+ methods) into cohesive modules with clear handoff contracts. Keep a stable facade API to avoid broad call-site churn.
## Target Decomposition (7 modules)
Based on method clustering analysis:
1. **polling/lifecycle** (~250 LOC) — `start`, `stop`, `poll`, `pollOnce`, `processNewCard`
2. **card-creation** (~350 LOC) — `createSentenceCard`, `setCardTypeFields`, `extractFields`, `processSentence`, field resolution helpers
3. **media-generation** (~200 LOC) — `generateAudio`, `generateImage`, filename generation, `generateMediaForMerge`
4. **field-grouping** (~900 LOC) — `triggerFieldGroupingForLastAddedCard`, `applyFieldGrouping`, `computeFieldGroupingMergedFields`, `buildFieldGroupingPreview`, `performFieldGroupingMerge`, `handleFieldGroupingAuto`, `handleFieldGroupingManual`, plus ~15 span/parse/normalize helpers
5. **duplicate-detection** (~100 LOC) — `findDuplicateNote`, `findFirstExactDuplicateNoteId`, search escaping
6. **ai-translation** (~100 LOC) — `translateSentenceWithAi`, `extractAiText`, `normalizeOpenAiBaseUrl`
7. **ui-feedback** (~150 LOC) — progress tracking, OSD notifications, status notifications
Plus a **facade** (`src/anki-integration/index.ts`) that re-exports the public API for backward compatibility.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Extract at least 6 modules matching the decomposition map above (7 recommended).
- [ ] #2 Keep a stable facade API in src/anki-integration/index.ts so external callers (main.ts, mining-service) don't change.
- [ ] #3 Field grouping cluster (~900 LOC) is extracted as its own module — it's the largest single concern.
- [ ] #4 AI/translation integration is extracted separately from card creation.
- [ ] #5 Each module defines explicit input/output types; no module exceeds 400 LOC unless justified in a TODO comment.
- [ ] #6 The 15 private state fields (pollingInterval, backoffMs, progressTimer, etc.) are managed through a shared internal state object or passed explicitly — not scattered across files as module-level lets.
- [ ] #7 Preserve existing external behavior for all config keys and session flow.
- [ ] #8 All existing tests pass; add focused unit tests for at least the field-grouping and card-creation modules.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
## Execution Notes
This task is self-contained — anki-integration.ts is a single class with a clear public API consumed by main.ts and mining-service. Internal restructuring doesn't affect other files as long as the facade is maintained.
**Should run first in Phase 2** because:
- It's the largest file (2,679 LOC vs 1,392 for main.ts)
- Its public API is narrow (class constructor + ~5 public methods)
- main.ts instantiates AnkiIntegration, so stabilizing its API before splitting main.ts avoids double-refactoring
## Key Risk
The class has 15 private state fields that create implicit coupling between methods. The `updateLastAddedFromClipboard` method alone is ~230 lines and touches polling state, media generation, and card updates. Extraction order matters: start with the leaf clusters (ai-translation, ui-feedback, duplicate-detection) and work inward toward the stateful core (polling, card-creation, field-grouping).
Started TASK-27.3 with a surgical extraction of the duplicate-detection cluster into `src/anki-integration-duplicate.ts` and refactoring `AnkiIntegration.findDuplicateNote()` to delegate all deck query, search escaping, and normalization logic to the new module while preserving behavior. This reduces `anki-integration.ts` by removing three private duplicate-parsing methods and keeps callsites unchanged. Remaining decomposition work still needed across polling/card-creation/field-grouping/notification clusters from the task map.
Second extraction pass completed: moved sentence-translation decision + AI fallback behavior out of `createSentenceCard` into `src/anki-integration/ai.ts` as `resolveSentenceBackText(...)`, with `AnkiIntegration` now delegating translation result generation to this function. This further isolates AI concerns from card-creation flow while keeping behavior and defaults intact.
Refactor for TASK-27.3 is complete and build passes after cleanup of ui-feedback delegation (src/anki-integration.ts, src/anki-integration-ui-feedback.ts).
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,88 @@
---
id: TASK-27.4
title: 'Split mpv-service.ts into protocol, transport, property, and facade layers'
status: Done
assignee:
- backend
created_date: '2026-02-13 17:13'
updated_date: '2026-02-15 00:31'
labels:
- 'owner:backend'
dependencies:
- TASK-27.1
references:
- src/core/services/mpv-service.ts
documentation:
- docs/architecture.md
parent_task_id: TASK-27
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Split mpv-service.ts (773 LOC) into thin, testable layers without changing wire protocol behavior.
**This task absorbs the scope of TASK-8** (Reduce MpvIpcClient deps interface and separate protocol from application logic). The work proceeds in two phases:
### Phase A (was TASK-8): Separate protocol from application logic
- Reduce the 22-property `MpvIpcClientDeps` interface to protocol-level concerns only
- Move application-level reactions (subtitle broadcast, overlay visibility sync, timing tracking) to event emitter or external listener pattern
- Make MpvIpcClient testable without mocking 22 callbacks
### Phase B: Physical file split
- Create submodules for: protocol parsing/dispatch, connection lifecycle/retry, property subscriptions/state mapping
- Wire the public facade to maintain API compatibility for all existing consumers
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 MpvIpcClient deps interface reduced to protocol-level concerns only (from current 22 properties).
- [x] #2 Application-level reactions (subtitle broadcast, overlay sync, timing) handled via event emitter or external listeners registered by main.ts.
- [x] #3 Create submodules for protocol parsing/dispatch, connection lifecycle/retry, and property subscriptions/state mapping.
- [x] #4 The public mpv service API (MpvClient interface) remains compatible for all existing consumers.
- [x] #5 MpvIpcClient is testable without mocking 22 callbacks — protocol layer tests need only socket-level mocks.
- [x] #6 Add at least one focused regression check for reconnect + property update flow.
- [x] #7 Document expected event flow in docs/structure-roadmap.md.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
## TASK-8 Absorption
TASK-8 and TASK-27.4 overlapped significantly:
- TASK-8: "Separate protocol from application logic" + shrink deps interface
- TASK-27.4: "Split into protocol, transport, property, and facade layers"
TASK-27.4 is TASK-8 + physical file splitting. Running them as separate tasks would mean touching the same file twice with the same goals. Now consolidated as Phase A (interface separation) and Phase B (file split) within a single task.
**TASK-8 should be marked as superseded** once this task begins.
## Dependency Note
Original plan listed TASK-8 as a dependency. Since TASK-8's scope is now absorbed here, the only remaining dependency is TASK-27.1 (inventory/contracts map).
Started prep: reviewed mpv-service coupling and prepared sequence for protocol/application split; no code split performed yet due current focus on keeping 27.2/27.3 sequencing compatible.
Known compatibility constraint: TASK-27.4 should proceed only after main.ts AppState migration is stable and after the app-level overlay/subsync/anki behavior contracts are preserved.
Milestone progress: extracted protocol buffer parsing into `src/core/services/mpv-protocol.ts`; `src/core/services/mpv-service.ts` now uses `splitMpvMessagesFromBuffer` in `processBuffer` and still delegates full message handling to existing handler. This is a small Phase B step toward protocol/dispatch separation.
Protocol extraction completed: full `MpvMessage` handling moved into `src/core/services/mpv-protocol.ts` via `splitMpvMessagesFromBuffer` + `dispatchMpvProtocolMessage`; `MpvIpcClient` now delegates all message parsing/dispatch through `MpvProtocolHandleMessageDeps` and resolves pending requests through `tryResolvePendingRequest`. `main.ts` wiring remains unchanged.
Updated `docs/structure-roadmap.md` expected mpv flow snapshot to reflect protocol parse/dispatch extraction (`splitMpvMessagesFromBuffer` + `dispatchMpvProtocolMessage`) and façade delegation path via `MpvProtocolHandleMessageDeps`.
Progress update: extracted socket connect/data/error/close/send/reconnect scheduling responsibilities into `MpvSocketTransport` (`src/core/services/mpv-transport.ts`) and wired `MpvIpcClient` to delegate connection lifecycle/send through it. Added `MpvSocketTransport` lifecycle tests in `src/core/services/mpv-transport.test.ts` covering connect/send/error/close behavior. Still in-progress on broader architectural refactor and API boundary reduction for `MpvIpcClient` deps beyond this transport split.
Added focused transport lifecycle regression coverage in `src/core/services/mpv-transport.test.ts`: connect/connect-idempotence, lifecycle callback ordering, and `shutdown()` resets connection/socket state. This covers reconnect/edge-case behavior at transport layer as part of criterion #6 toward protocol + lifecycle regression protection.
Added mpv-service unit regression for close lifecycle: `MpvIpcClient onClose resolves outstanding pending requests and triggers reconnect scheduling path via client transport callbacks (`src/core/services/mpv-service.test.ts`). This complements transport-level lifecycle tests for reconnect behavior regression coverage.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Split mpv-service internals into protocol, transport, and property/state-mapping boundaries; reduced MpvIpcClient deps to protocol-level concerns with event-based app reactions in main.ts; added mpv-service/mpv-transport tests for protocol dispatch, reconnect scheduling, and lifecycle regressions; documented expected event flow in docs/structure-roadmap.md.
Added mpv-service reconnect regression test that asserts a reconnect lifecycle replays mpv property bootstrap commands (`secondary-sub-visibility` reset, `observe_property`, and initial `get_property` state fetches) during reconnection.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,65 @@
---
id: TASK-27.5
title: Split renderer positioning.ts into focused modules
status: To Do
assignee:
- frontend
created_date: '2026-02-13 17:13'
updated_date: '2026-02-13 21:17'
labels:
- refactor
- renderer
dependencies:
- TASK-27.1
references:
- src/renderer/renderer.ts
- src/renderer/subtitle-render.ts
- src/renderer/positioning.ts
- src/renderer/handlers/keyboard.ts
- src/renderer/handlers/mouse.ts
documentation:
- docs/architecture.md
parent_task_id: TASK-27
priority: low
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Split positioning.ts (513 LOC) — the only oversized file in the renderer — into focused modules. The rest of the renderer structure is already well-organized and does not need reorganization.
## Current Renderer State (already good)
- `renderer.ts` (241 lines) — pure composition, well-factored
- `state.ts` (132 lines), `context.ts` (14 lines), `subtitle-render.ts` (206 lines) — reasonable sizes
- `handlers/keyboard.ts` (238 lines), `handlers/mouse.ts` (271 lines) — focused
- `modals/` — 4 modal implementations, each self-contained
- Communication already uses explicit `ctx` pattern and function parameters, not globals
## What Actually Needs Work
`positioning.ts` mixes visible overlay positioning, invisible overlay positioning, MPV subtitle render metrics layout, and position persistence. These are distinct concerns that should be separate modules.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Split positioning.ts into at least 2 focused modules (e.g., visible-positioning and invisible-positioning, or by concern: layout, persistence, metrics).
- [ ] #2 No module exceeds 300 LOC.
- [ ] #3 Existing overlay behavior (subtitle positioning, drag, invisible layer metrics) unchanged.
- [ ] #4 renderer.ts imports stay clean — use an index re-export if needed.
- [ ] #5 Manual validation: subtitle positioning, drag/select, invisible layer alignment all work correctly.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
## Downscope Rationale
Original task proposed creating src/renderer/subtitles/, src/renderer/input/, src/renderer/state/ directories and introducing "explicit interfaces/events for keyboard/mouse/positioning/state updates to avoid global mutable coupling."
Review found:
1. The renderer already uses a `ctx` composition pattern — no global mutable coupling exists
2. Files are already organized by concern (handlers/, modals/, utils/)
3. Only positioning.ts (513 LOC) exceeds the 400 LOC threshold
4. Creating new directory structures for files under 300 lines adds churn without proportional benefit
Reduced scope to: split positioning.ts only. If future feature work (JLPT tagging, frequency highlighting) adds significant renderer complexity, a broader reorganization can be reconsidered then.
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,52 @@
---
id: TASK-27.6
title: Add structural quality gates for file size and complexity
status: To Do
assignee:
- architect
created_date: '2026-02-13 17:13'
updated_date: '2026-02-13 21:19'
labels:
- 'owner:architect'
- 'owner:backend'
- 'owner:frontend'
dependencies:
- TASK-27.1
- TASK-27.2
- TASK-27.3
- TASK-27.4
- TASK-27.5
parent_task_id: TASK-27
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add automated safeguards so oversized/complex files are caught early and refactor progress is measurable.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Extend check-main-lines gate script to accept any file path and apply it to: src/main.ts, src/anki-integration.ts (or src/anki-integration/index.ts), src/core/services/mpv-service.ts, src/renderer/positioning.ts, src/config/service.ts.
- [ ] #2 Define per-file thresholds (suggested: 400 LOC default, 600 for config/service.ts, justified exceptions documented in the script).
- [ ] #3 Add ESLint complexity rule (or lightweight proxy) with per-directory thresholds — at minimum for src/core/services/ and src/anki-integration/.
- [ ] #4 Create a clear exception process for justified threshold breaks: comment in code with expiration date and owner.
- [ ] #5 Document thresholds in docs/structure-roadmap.md.
- [ ] #6 Clarify enforcement: local-only (npm script) or CI-enforced. If CI, add to the CI pipeline config.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
## Review Additions
Original task omitted anki-integration.ts from the gated file list — it's the largest file at 2,679 LOC and the primary target of TASK-27.3. Added to AC#1.
The existing check-main-lines.sh is a simple `wc -l` check. Consider augmenting with:
- ESLint `complexity` rule for cyclomatic complexity
- Method count per file (proxy for cohesion)
- Import count per file (proxy for coupling)
Raw line count is better than nothing but doesn't catch files that are long because of well-structured, low-complexity code (like config/definitions.ts at 479 LOC which is just defaults).
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,48 @@
---
id: TASK-27.7
title: >-
Decompose anki-integration.ts core into domain modules (field-grouping,
card-creation, polling)
status: To Do
assignee: []
created_date: '2026-02-15 07:00'
labels:
- refactor
- anki
- architecture
dependencies:
- TASK-27.3
references:
- src/anki-integration.ts
- src/anki-integration-duplicate.ts
- src/anki-integration-ui-feedback.ts
- src/anki-integration/ai.ts
parent_task_id: TASK-27
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
TASK-27.3 extracted leaf clusters (duplicate-detection 102 LOC, ai-translation 158 LOC, ui-feedback 107 LOC) but the core class remains at 2935 LOC. The heavy decomposition from the original TASK-27.3 plan was never executed.
Remaining extractions from the original plan:
1. **field-grouping** (~900 LOC) — `triggerFieldGroupingForLastAddedCard`, `applyFieldGrouping`, `computeFieldGroupingMergedFields`, `buildFieldGroupingPreview`, `performFieldGroupingMerge`, `handleFieldGroupingAuto`, `handleFieldGroupingManual`, plus ~15 span/parse/normalize helpers
2. **card-creation** (~350 LOC) — `createSentenceCard`, `setCardTypeFields`, `extractFields`, `processSentence`, field resolution helpers
3. **polling/lifecycle** (~250 LOC) — `start`, `stop`, `poll`, `pollOnce`, `processNewCard`
Also consolidate the scattered extraction files into `src/anki-integration/`:
- `src/anki-integration-duplicate.ts``src/anki-integration/duplicate.ts`
- `src/anki-integration-ui-feedback.ts``src/anki-integration/ui-feedback.ts`
- `src/anki-integration/ai.ts` (already there)
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 anki-integration.ts reduced below 800 LOC (facade + private state wiring only)
- [ ] #2 Field-grouping cluster (~900 LOC) extracted as its own module under src/anki-integration/
- [ ] #3 Card-creation and polling/lifecycle extracted as separate modules
- [ ] #4 All extracted modules consolidated under src/anki-integration/ directory
- [ ] #5 Existing facade API preserved — external callers unchanged
- [ ] #6 All existing tests pass; build compiles cleanly
<!-- AC:END -->

View File

@@ -0,0 +1,190 @@
---
id: TASK-28
title: Add SQLite-backed immersion tracking for mining sessions
status: To Do
assignee: []
created_date: '2026-02-13 17:52'
updated_date: '2026-02-13 19:37'
labels:
- analytics
- backend
- database
- immersion
dependencies: []
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
## Updated scope
Implement SQLite-first immersion tracking for mining sessions optimized for speed/size and designed for a future external DB adapter.
## Runtime defaults
- Flush batch policy: flush every `25` telemetry points or `500ms`.
- SQLite: `journal_mode = WAL`, `synchronous = NORMAL`, `foreign_keys = ON`, `busy_timeout = 2500ms`.
- Query target: `<150ms p95` for session/video/time-window reads at ~1M rows.
- In-memory queue cap: `1000` events; define explicit overflow behavior.
- Retention: events `7d`, telemetry `30d`, daily rollups `365d`, monthly rollups `5y`; prune on startup + every `24h`, vacuum on idle weekly.
## Concrete v1 schema (compact, size-aware)
```sql
CREATE TABLE imm_schema_version(
schema_version INTEGER PRIMARY KEY,
applied_at_ms INTEGER NOT NULL
);
CREATE TABLE imm_videos(
video_id INTEGER PRIMARY KEY AUTOINCREMENT,
video_key TEXT NOT NULL UNIQUE,
canonical_title TEXT NOT NULL,
source_type INTEGER NOT NULL,
source_path TEXT,
source_url TEXT,
duration_ms INTEGER NOT NULL CHECK(duration_ms>=0),
file_size_bytes INTEGER CHECK(file_size_bytes>=0),
codec_id INTEGER, container_id INTEGER,
width_px INTEGER, height_px INTEGER, fps_x100 INTEGER,
bitrate_kbps INTEGER, audio_codec_id INTEGER,
hash_sha256 TEXT, screenshot_path TEXT,
metadata_json TEXT,
created_at_ms INTEGER NOT NULL, updated_at_ms INTEGER NOT NULL
);
CREATE TABLE imm_sessions(
session_id INTEGER PRIMARY KEY AUTOINCREMENT,
session_uuid TEXT NOT NULL UNIQUE,
video_id INTEGER NOT NULL,
started_at_ms INTEGER NOT NULL, ended_at_ms INTEGER,
status INTEGER NOT NULL,
locale_id INTEGER, target_lang_id INTEGER,
difficulty_tier INTEGER, subtitle_mode INTEGER,
created_at_ms INTEGER NOT NULL, updated_at_ms INTEGER NOT NULL,
FOREIGN KEY(video_id) REFERENCES imm_videos(video_id)
);
CREATE TABLE imm_session_telemetry(
telemetry_id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id INTEGER NOT NULL,
sample_ms INTEGER NOT NULL,
total_watched_ms INTEGER NOT NULL DEFAULT 0,
active_watched_ms INTEGER NOT NULL DEFAULT 0,
lines_seen INTEGER NOT NULL DEFAULT 0,
words_seen INTEGER NOT NULL DEFAULT 0,
tokens_seen INTEGER NOT NULL DEFAULT 0,
cards_mined INTEGER NOT NULL DEFAULT 0,
lookup_count INTEGER NOT NULL DEFAULT 0,
lookup_hits INTEGER NOT NULL DEFAULT 0,
pause_count INTEGER NOT NULL DEFAULT 0,
pause_ms INTEGER NOT NULL DEFAULT 0,
seek_forward_count INTEGER NOT NULL DEFAULT 0,
seek_backward_count INTEGER NOT NULL DEFAULT 0,
media_buffer_events INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY(session_id) REFERENCES imm_sessions(session_id) ON DELETE CASCADE
);
CREATE TABLE imm_session_events(
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
session_id INTEGER NOT NULL,
ts_ms INTEGER NOT NULL,
event_type INTEGER NOT NULL,
line_index INTEGER,
segment_start_ms INTEGER,
segment_end_ms INTEGER,
words_delta INTEGER NOT NULL DEFAULT 0,
cards_delta INTEGER NOT NULL DEFAULT 0,
payload_json TEXT,
FOREIGN KEY(session_id) REFERENCES imm_sessions(session_id) ON DELETE CASCADE
);
CREATE TABLE imm_daily_rollups(
rollup_day INTEGER NOT NULL,
video_id INTEGER,
total_sessions INTEGER NOT NULL DEFAULT 0,
total_active_min REAL NOT NULL DEFAULT 0,
total_lines_seen INTEGER NOT NULL DEFAULT 0,
total_words_seen INTEGER NOT NULL DEFAULT 0,
total_tokens_seen INTEGER NOT NULL DEFAULT 0,
total_cards INTEGER NOT NULL DEFAULT 0,
cards_per_hour REAL,
words_per_min REAL,
lookup_hit_rate REAL,
PRIMARY KEY (rollup_day, video_id)
);
CREATE TABLE imm_monthly_rollups(
rollup_month INTEGER NOT NULL,
video_id INTEGER,
total_sessions INTEGER NOT NULL DEFAULT 0,
total_active_min REAL NOT NULL DEFAULT 0,
total_lines_seen INTEGER NOT NULL DEFAULT 0,
total_words_seen INTEGER NOT NULL DEFAULT 0,
total_tokens_seen INTEGER NOT NULL DEFAULT 0,
total_cards INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY (rollup_month, video_id)
);
CREATE INDEX idx_sessions_video_started ON imm_sessions(video_id, started_at_ms DESC);
CREATE INDEX idx_sessions_status_started ON imm_sessions(status, started_at_ms DESC);
CREATE INDEX idx_telemetry_session_sample ON imm_session_telemetry(session_id, sample_ms DESC);
CREATE INDEX idx_events_session_ts ON imm_session_events(session_id, ts_ms DESC);
CREATE INDEX idx_events_type_ts ON imm_session_events(event_type, ts_ms DESC);
CREATE INDEX idx_rollups_day_video ON imm_daily_rollups(rollup_day, video_id);
CREATE INDEX idx_rollups_month_video ON imm_monthly_rollups(rollup_month, video_id);
```
Notes
- Integer enums keep hot rows narrow and fast; resolve labels in app layer.
- JSON fields are only for non-core overflow attributes (bounded by policy).
- `source_type`/`status`/`subtitle_mode` are compact enums and keep strings out of high-frequency rows.
- This schema is a v1 contract for TASK-32 adapter work later.
## Future portability
- Keep all analytics logic behind a storage interface in TASK-32.
- Keep raw SQL/DDL details inside adapters.
## Execution principle
- Tracking is strictly asynchronous and must never block hot paths.
- Tokenization/rendering pipelines must not await DB operations.
- If tracker queue is saturated, user experience must remain unchanged; telemetry may be dropped with bounded loss and explicit internal warning/logging.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 A SQLite database schema is defined and created automatically (or initialized on startup) for immersion tracking if not present.
- [ ] #2 Recorded events persist at least the following fields per session/item: video name, video directory/URL, video length, lines seen, words/tokens seen, cards mined.
- [ ] #3 Tracking defaults to storing data in SQLite without requiring additional DB setup for local usage.
- [ ] #4 Additional extractable metadata from video files is captured and stored when available (e.g., dimensions, duration, codec, fps, file size/hash, optional screenshot path).
- [ ] #5 Tracking does not degrade mining throughput and handles duplicate/missing metadata fields safely.
- [ ] #6 Query/read paths exist to support future richer statistics generation (e.g., totals by video, throughput, quality metrics).
- [ ] #7 Schema design and implementation include clear migration/versioning strategy for future fields.
- [ ] #8 Schema uses compact numeric/tiny integer types where practical and minimizes repeated TEXT payloads to balance write/read speed and file size.
- [ ] #9 High-frequency writes are batched (or buffered) with periodic checkpoints so writes do not fsync per telemetry point.
- [ ] #10 Event retention and rollup strategy is documented: raw event retention, summary tables, and compaction policy to bound DB size.
- [ ] #11 Query performance targets are addressed with index strategy and a documented plan for index coverage (session-by-video, time-window, event-type, card/count lookups).
- [ ] #12 Migration/versioning strategy supports future backend portability without requiring analytics-layer rewrite (schema version table + adapter boundary specified).
- [ ] #13 Task defines operational defaults: flush every 25 events or 500ms, WAL+NORMAL, queue cap of 1000 rows, in-flight payload cap of 256B, and explicit overflow behavior.
- [ ] #14 Task defines retention defaults and maintenance cadence: events 7d, telemetry 30d, daily 365d, monthly 5y, startup + 24h prune and idle-weekly vacuum.
- [ ] #15 Task documents expected query performance target (150ms p95) and storage growth guardrails for typical local usage up to ~1M events.
- [ ] #16 #13 Concrete DDL (tables + indexes + pragmas) is captured in task docs and used as implementation reference.
- [ ] #17 #14 v1 retention policy, batch policy, and maintenance schedule are explicitly implemented and configurable.
- [ ] #18 #15 Query templates for timeline/throughput/rollups are defined in implementation docs.
- [ ] #19 #16 Queue cap, payload cap, and overflow behavior are implemented and documented.
- [ ] #20 #20 All tracking writes are strictly asynchronous and non-blocking from tokenization/render loops; hot paths must never await persistence.
- [ ] #21 #21 Queue saturation handling is explicit: bounded queue with deterministic policy (drop oldest, drop newest, or backpressure) and no impact on on-screen token colorization or line rendering.
- [ ] #22 #22 Tracker failures/timeouts are swallowed from hot path with optional background retry and failure counters/logging for observability.
<!-- AC:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 SQLite tracking table(s), migration history table, and indices created as part of startup or init path.
- [ ] #2 Unit/integration coverage (or validated test plan) confirms minimum fields are persisted and retrievable.
- [ ] #3 README or docs updated with storage schema, retention defaults, and extension points.
- [ ] #4 Migration and retention defaults are documented (pruning frequency, rollup cadence, expected disk growth profile).
- [ ] #5 Performance-safe write path behavior is documented (batch commit interval/size, WAL mode, sync mode).
- [ ] #6 A follow-up ticket captures and tracks non-SQLite backend abstraction work.
- [ ] #7 The implementation doc includes the exact schema, migration version, and index set.
- [ ] #8 Performance-size tradeoffs are clearly documented (batching, enum columns, bounded JSON, TTL retention).
- [ ] #9 Rollup/retention behavior is in place with explicit defaults and cleanup cadence.
<!-- DOD:END -->

View File

@@ -0,0 +1,50 @@
---
id: TASK-29
title: Add Anilist integration for post-watch updates
status: To Do
assignee: []
created_date: '2026-02-13 17:57'
labels:
- anilist
- anime
- integration
- electron
- api
dependencies: []
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add Anilist integration so the app can update user anime progress after watching, by porting the core functionality of `AzuredBlue/mpv-anilist-updater` into the Electron app. The initial implementation should focus on reliable sync of watch status/progress and be structured to support future Anilist features beyond updates.
Requirements:
- Port the core behavior from `AzuredBlue/mpv-anilist-updater` needed for post-watch syncing into the Electron architecture.
- Authenticate and persist Anilist credentials securely in the desktop environment.
- Identify anime/media item and track watched status/progress based on existing video/session data.
- Trigger updates to Anilist after watch milestones/session completion.
- Queue and retry updates safely when offline or API errors occur.
- Avoid blocking playback/mining operations while syncing.
- Design Anilist integration as a dedicated, testable module/service so additional Anilist features can be added later (e.g., status actions, favorites, episode tracking enhancements).
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Application can authenticate with Anilist and store tokens securely for desktop user sessions.
- [ ] #2 Anilist update flow from existing local watch/session metadata can update animes watched progress after watching.
- [ ] #3 Core functionality equivalent to mpv-anilist-updater is implemented for this use case (progress/status sync) inside the Electron app.
- [ ] #4 A background/in-process queue handles transient API failures and retries without losing updates.
- [ ] #5 Sync updates are non-blocking and do not degrade normal playback/mining behavior.
- [ ] #6 Anilist integration code is modularized to allow future feature additions without major refactor (clear service boundaries/interfaces).
- [ ] #7 Error states and duplicate/duplicate-inconsistent updates are handled deterministically (idempotent where practical).
<!-- AC:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Core Anilist service module exists and is wired into application flow for post-watch updates.
- [ ] #2 OAuth/token lifecycle is implemented with safe local persistence and revocation/logout behavior.
- [ ] #3 A retry/backoff and dead-letter strategy for failed syncs is implemented.
- [ ] #4 User-visible settings/docs explain how to connect/manage Anilist and what data is synced.
- [ ] #5 At least smoke/integration coverage (or validated test plan) for mapping and sync flow is in place.
<!-- DOD:END -->

View File

@@ -0,0 +1,28 @@
---
id: TASK-3
title: move invisible subtitles
status: Done
assignee:
- codex
created_date: '2026-02-11 03:34'
updated_date: '2026-02-11 04:28'
labels: []
dependencies: []
ordinal: 1000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add keybinding that will toggle edit mode on the invisible subtitles allowing for fine-grained control over positioning. use arrow keys and vim hjkl for motion and enter/ctrl+s to save and esc to cancel
<!-- SECTION:DESCRIPTION:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
- Implemented invisible subtitle position edit mode toggle with movement/save/cancel controls.
- Added persistence for invisible subtitle offsets (`invisibleOffsetXPx`, `invisibleOffsetYPx`) alongside existing `yPercent` subtitle position state.
- Updated edit mode visuals to highlight invisible subtitle text using the same styling as debug visualization.
- Removed the edit-mode dashed bounding box.
- Updated top HUD instruction text to reference arrow keys only (while keeping `hjkl` movement support).
<!-- SECTION:NOTES:END -->

View File

@@ -0,0 +1,28 @@
---
id: TASK-31
title: Add optional Jellyfin integration with basic streaming/ playback features
status: To Do
assignee: []
created_date: '2026-02-13 18:38'
labels: []
dependencies: []
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Implement optional Jellyfin integration so SubMiner can act as a lightweight Jellyfin client similar to jellyfin-mpv-shim. The feature should support connecting to Jellyfin servers, browsing playable media, and launching playback through SubMiner, including direct play when possible and transparent transcoding when required.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Add a configurable Jellyfin integration path that can be enabled/disabled without impacting core non-Jellyfin functionality.
- [ ] #2 Support authenticating against a user-selected Jellyfin server (server URL + credentials/token) and securely storing/reusing connection settings.
- [ ] #3 Allow discovery or manual selection of movies/tv shows/music libraries and playback items from the connected Jellyfin server.
- [ ] #4 Enable playback from Jellyfin items via existing player pipeline with a dedicated selection/launch flow.
- [ ] #5 Honor Jellyfin playback options so direct play is attempted first when media/profiles are compatible.
- [ ] #6 Fall back to Jellyfin-managed transcoding when direct play is not possible, passing required transcode parameters to the player.
- [ ] #7 Preserve useful Jellyfin metadata/features during playback: title/season/episode, subtitles, audio track selection, and playback resume markers where available.
- [ ] #8 Add handling for common failure modes (invalid credentials, token expiry, server offline, transcoding/stream errors) with user-visible status/errors.
- [ ] #9 Document setup and limitations (what works vs what is optional) in project documentation, and add tests or mocks that validate key integration logic and settings handling.
<!-- AC:END -->

View File

@@ -0,0 +1,97 @@
---
id: TASK-32
title: Add pluggable immersion tracking persistence layer for non-SQLite backends
status: To Do
assignee: []
created_date: '2026-02-13 19:30'
updated_date: '2026-02-14 00:46'
labels:
- analytics
- backend
- database
- architecture
- immersion
dependencies:
- TASK-28
priority: low
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
## Scope
Create a storage abstraction for immersion analytics so existing SQLite-first tracking can evolve to external backends (PostgreSQL/MySQL/other) without rewriting core analytics behavior.
## Backend portability and performance contract
- Define a canonical interface that avoids SQL dialect leakage and returns stable query result shapes for session, video, and trend analytics.
- Decouple all `TASK-28` persistence callers from raw SQLite access behind a repository/adapter boundary.
- Keep domain model and query DTOs backend-agnostic; SQL specifics live in adapters.
## Target architecture
- Add provider config: `storage.provider` default `sqlite`.
- Use dependency injection/service factory to select adapter at startup.
- Add adapter contract tests to validate `Session`, `Video`, `Telemetry`, `Event`, and `Rollup` operations behave identically across backends.
- Include migration and rollout contract with compatibility/rollback notes.
## Numeric operational constraints to preserve (from TASK-28)
- All adapters must support write batching (or equivalent) with flush cadence equivalent to 25 records or 500ms.
- All adapters must enforce bounded in-memory write queue (default 1000 rows) and explicit overflow policy.
- All adapters should preserve index/query shapes needed for ~150ms p95 read targets on session/video/time-window queries at ~1M events.
- All adapters should support retention semantics equivalent to: events 7d, telemetry 30d, daily rollups 365d, monthly rollups 5y, and prune-on-startup + every 24h + idle vacuum schedule.
## Async and isolation constraint
- Adapter API must support enqueue/write-queue semantics so tokenization/render loops never block on persistence.
- Background worker owns DB I/O; storage adapter exposes non-blocking API surface to tracking pipeline.
Acceptance Criteria:
--------------------------------------------------
- [ ] #1 Define a stable `ImmersionTrackingStore` interface covering session lifecycle, telemetry counters, event writes, and analytics queries (timeline, video stats, throughput, and streak/trend summaries).
- [ ] #2 Implement a DI-bound storage repository that encapsulates all DB interaction behind the interface.
- [ ] #3 Refactor `TASK-28`-related data writes/reads in the tracking pipeline to depend on the abstraction (not raw SQLite calls).
- [ ] #4 Document migration path from current schema to backend-adapter-based persistence.
- [ ] #5 Add config for storage provider (`sqlite` default) and connection options, with validation and clear fallback behavior.
- [ ] #6 Create adapter contracts for at least one external SQL engine target (PostgreSQL/MySQL contract/schema mapping), even if execution is deferred with a follow-up task.
- [ ] #7 Add tests or validation plan for adapter boundary behavior (mock adapter + contract tests, and error handling behavior).
- [ ] #8 Expose retention and write profile defaults in backend contracts: 25 events/500ms batching, queue cap 1000, event payload cap 256B, overflow policy, and retention windows equivalent to TASK-28.
- [ ] #9 Preserve performance contract semantics in adapters: query/index assumptions for sub-150ms p95 local reads on ~1M event scale and same read-path shapes as TASK-28.
Definition of Done:
--------------------------------------------------
- [ ] #1 Storage interface with required method signatures and query contracts is documented in code and backlog docs.
- [ ] #2 Default SQLite adapter remains the primary implementation and passes existing/ planned immersion tracking expectations.
- [ ] #3 Non-SQLite implementation path is explicitly represented in config and adapter scaffolding.
- [ ] #4 Tracking pipeline is fully storage-engine agnostic and can support a new adapter without schema churn.
- [ ] #5 Migration and rollout strategy is documented (phased migration, backfill, compatibility behavior, rollback plan).
- [ ] #6 Backend contract includes operational and storage-growth semantics that preserve performance/size behavior from TASK-28.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Define a stable `ImmersionTrackingStore` interface covering session lifecycle, telemetry counters, event writes, and analytics queries (timeline, video stats, throughput, and streak/trend summaries).
- [ ] #2 Implement a DI-bound storage repository that encapsulates all DB interaction behind the interface.
- [ ] #3 Refactor `TASK-28`-related data writes/reads in the tracking pipeline to depend on the abstraction (not raw SQLite calls).
- [ ] #4 Document migration path from current schema to backend-adapter-based persistence.
- [ ] #5 Add config for storage provider (`sqlite` default) and connection options, with validation and clear fallback behavior.
- [ ] #6 Create adapter contracts for at least one external SQL engine target (PostgreSQL/MySQL contract/schema mapping), even if execution is deferred with a follow-up task.
- [ ] #7 Add tests or validation plan for adapter boundary behavior (mock adapter + contract tests, and error handling behavior).
- [ ] #8 Expose retention and write profile defaults in backend contracts: 25 events/500ms batching, queue cap 1000, event payload cap 256B, overflow policy, and retention windows equivalent to TASK-28.
- [ ] #9 Preserve performance contract semantics in adapters: query/index assumptions for sub-150ms p95 local reads on ~1M event scale and same read-path shapes as TASK-28.
- [ ] #10 #9 Storage interface must expose async/fire-and-forget write contract for telemetry/event ingestion (no blocking calls available to UI/tokenization path).
- [ ] #11 #10 Adapter boundary must guarantee: persistence errors from tracker are observed internally and surfaced as background diagnostics, never bubbled into tokenization/render execution path.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Priority deferred from medium to low: this is premature until TASK-28 (SQLite tracking) ships and a concrete second backend need emerges. The SQLite-first design in TASK-28 already accounts for future portability via schema contracts and adapter boundaries. Revisit after TASK-28 has been in use.
<!-- SECTION:NOTES:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Storage interface with required method signatures and query contracts is documented in code and backlog docs.
- [ ] #2 Default SQLite adapter remains the primary implementation and passes existing/ planned immersion tracking expectations.
- [ ] #3 Non-SQLite implementation path is explicitly represented in config and adapter scaffolding.
- [ ] #4 Tracking pipeline is fully storage-engine agnostic and can support a new adapter without schema churn.
- [ ] #5 Migration and rollout strategy is documented (phased migration, backfill, compatibility behavior, rollback plan).
- [ ] #6 Backend contract includes operational and storage-growth semantics that preserve performance/size behavior from TASK-28.
<!-- DOD:END -->

View File

@@ -0,0 +1,39 @@
---
id: TASK-33
title: Restrict mpv socket connection logs to debug mode in Electron
status: To Do
assignee: []
created_date: '2026-02-13 19:39'
labels:
- electron
- logging
- mpv
- frontend
- quality
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
In normal operation, Electron should not spam logs while waiting for mpv socket connection. Emit mpv socket connection logs only when app logging level is debug. In regular use, keep connection attempts silent while waiting, and log exactly once when the socket connects successfully.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 When logging level is not debug, do not emit logs for mpv socket connection attempts, retries, or wait loops.
- [ ] #2 In non-debug mode, the app should silently wait for mpv socket readiness instead of printing connection-loop noise.
- [ ] #3 Log exactly one concise INFO log entry when a mpv socket connection succeeds (e.g., per lifecycle attempt/session).
- [ ] #4 In debug mode, keep existing detailed connection attempt/retry logs to aid diagnosis.
- [ ] #5 No functional change to socket connection/retry behavior besides logging level gating.
- [ ] #6 If connection fails and retries continue, keep a debug-only or final-error log policy consistent with existing logging severity conventions.
<!-- AC:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 No connection-attempt/connect-wait logs are produced in non-debug mode.
- [ ] #2 Exactly one success log is produced when connection is established in non-debug mode.
- [ ] #3 Debug mode continues to emit detailed connection logs as before.
- [ ] #4 Behavior is validated across cold-start and reconnect attempts (single success log per successful connect event).
<!-- DOD:END -->

View File

@@ -0,0 +1,45 @@
---
id: TASK-34
title: >-
Add in-app episode browser (Ctrl+E) for local files and Jellyfin-ready
metadata
status: To Do
assignee: []
created_date: '2026-02-13 22:12'
updated_date: '2026-02-13 22:13'
labels: []
dependencies: []
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Implement an in-app episode browser invoked by Ctrl+E when mpv is open and connected to the Electron UI. The viewer should use the currently supplied directory (if app was launched with one) or fallback to the parent directory of the currently playing video. It should enumerate all available video files in target directory and sort them deterministically, then display them in a polished list/gallery with thumbnails in the Electron UI. Thumbnail behavior should prioritize existing matching images in the directory; otherwise generate thumbnails asynchronously in the background and update the modal as they become available. The same menu infrastructure should be shared with the planned Jellyfin integration and designed so it can display additional Jellyfin-sourced metadata without UI rewrites. It should also support launching/using an alternate picker mode compatible with external launcher UX patterns (e.g., via fzf/rofi), showing the same episode list/metadata in those contexts.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Pressing Ctrl+E in connected mode opens an episode viewer modal and closes/overlays correctly in the Electron app.
- [ ] #2 Episode browser source directory is resolved from CLI-provided path when present, else from currently playing video's parent directory.
- [ ] #3 Browser enumerates all supported video files in target directory and sorts them deterministically (e.g., natural/season-episode aware when possible).
- [ ] #4 Episode list/gallery renders a consistent, usable layout with title, position/index, and thumbnail placeholders.
- [ ] #5 If a thumbnail image file with matching name exists in the directory, it is used without background generation.
- [ ] #6 For files without matching thumbnails, background thumbnail generation runs and updates entries as images become available in the modal.
- [ ] #7 Thumbnail generation is cancellable/abortable and does not block UI interaction.
- [ ] #8 The same episode viewer component/path can be reused by Jellyfin integration and can accept extended metadata payloads (e.g., show title, season/episode, runtime, description).
- [ ] #9 `fzf`/`rofi`-compatible episode picker mode is available so the same episode set can be browsed outside the Electron modal using either backend output format.
- [ ] #10 Episode item ordering, labels, and metadata shown in fzf/rofi mode match the in-app sorting and identity scheme.
- [ ] #11 Both picker modes (Electron modal and fzf/rofi) resolve source directory using the same CLI/parent-of-current-video precedence rules.
- [ ] #12 When invoked through fzf/rofi, the selected episode can be played and returns focus/flow safely to the Electron-mpv workflow.
<!-- AC:END -->
## Definition of Done
<!-- DOD:BEGIN -->
- [ ] #1 Feature supports Ctrl+E invocation from connected mpv session.
- [ ] #2 Directory fallback behavior is implemented and validated with both passed-in and default paths.
- [ ] #3 Video listing excludes unsupported formats and is correctly sorted.
- [ ] #4 Existing local thumbnail matching works for common image/video naming patterns.
- [ ] #5 Background thumbnail generation works asynchronously and updates UI in-place.
- [ ] #6 UI is ready for Jellyfin metadata fields and does not require structural rewrite for server-provided data.
- [ ] #7 No regressions in existing mpv-Electron controls for standard playback.
<!-- DOD:END -->

View File

@@ -0,0 +1,46 @@
---
id: TASK-35
title: Add CI/CD pipeline for automated testing and quality gates
status: To Do
assignee: []
created_date: '2026-02-14 00:57'
labels:
- infrastructure
- ci
- quality
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add a GitHub Actions CI pipeline that runs on PRs and pushes to main. The project already has 23 test files (67+ tests) and a `check-main-lines.sh` quality gate script with progressive line-count targets, but none of this runs automatically.
## Motivation
Without CI, regressions in tests or quality gate violations are only caught manually. As the refactoring effort (TASK-27.x) accelerates and new features land, automated checks become essential.
## Scope
1. **Test runner**: Run `pnpm test` on every PR and push to main
2. **Quality gates**: Run `check-main-lines.sh` to enforce main.ts line-count targets
3. **Type checking**: Run `tsc --noEmit` to catch type errors
4. **Build verification**: Run `make build` to confirm the app compiles
5. **Platform matrix**: Linux at minimum (primary target), macOS if feasible
## Implementation notes
- The project uses pnpm for package management
- Tests use Node's built-in test runner
- Build uses Make + tsc + electron-builder
- Consider caching node_modules and pnpm store for speed
- MeCab is a native dependency needed for some tests — document or skip if unavailable in CI
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 GitHub Actions workflow runs pnpm test on every PR and push to main.
- [ ] #2 Quality gate script (check-main-lines.sh) runs and fails the build if line count exceeds threshold.
- [ ] #3 tsc --noEmit type check passes as a CI step.
- [ ] #4 Build step (make build) completes without errors.
- [ ] #5 CI results are visible on PR checks.
- [ ] #6 Pipeline completes in under 5 minutes for typical changes.
<!-- AC:END -->

View File

@@ -0,0 +1,49 @@
---
id: TASK-36
title: Add structured logging with configurable verbosity levels
status: To Do
assignee: []
created_date: '2026-02-14 00:59'
labels:
- infrastructure
- developer-experience
- observability
dependencies: []
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Replace ad-hoc console.log/console.error calls throughout the codebase with a lightweight structured logger that supports configurable verbosity levels (debug, info, warn, error).
## Motivation
- TASK-33 (restrict mpv socket logs) is a symptom of a broader problem: no log-level filtering
- Debugging production issues requires grepping through noisy output
- Users report log spam in normal operation
- No way to enable verbose logging for bug reports without code changes
## Scope
1. Create a minimal logger module (no external dependencies needed) with `debug`, `info`, `warn`, `error` levels
2. Add a config option for log verbosity (default: `info`)
3. Add a CLI flag `--verbose` / `--debug` to override
4. Migrate existing console.log/error calls to use the logger
5. Include context tags (service name, operation) in log output for filterability
## Design constraints
- Zero external dependencies — use a simple wrapper over console methods
- Must not impact hot-path performance (subtitle rendering, tokenization)
- Log level should be changeable at runtime via config hot-reload if that feature exists
- TASK-33 becomes trivial once this lands (mpv socket logs become `debug` level)
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 A logger module exists with debug/info/warn/error levels.
- [ ] #2 Config option controls default verbosity level.
- [ ] #3 CLI --verbose/--debug flag overrides config.
- [ ] #4 Existing console.log/error calls in core services are migrated to structured logger.
- [ ] #5 MPV socket connection logs use debug level (resolves TASK-33 implicitly).
- [ ] #6 Log output includes source context (service/module name).
- [ ] #7 No performance regression on hot paths (rendering, tokenization).
<!-- AC:END -->

View File

@@ -0,0 +1,44 @@
---
id: TASK-37
title: Add error boundary and recovery in renderer overlay
status: To Do
assignee: []
created_date: '2026-02-14 01:01'
labels:
- renderer
- reliability
- error-handling
dependencies: []
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add a top-level error boundary in the renderer orchestrator that catches unhandled errors in modals and rendering logic, displays a user-friendly error message, and recovers without crashing the overlay.
## Motivation
If a renderer modal throws (e.g., jimaku API timeout, DOM manipulation error, malformed subtitle data), the entire overlay can become unresponsive. Since the overlay is transparent and positioned over mpv, a crashed renderer is invisible but blocks interaction.
## Scope
1. Wrap the renderer orchestrator's modal dispatch and subtitle rendering in try/catch boundaries
2. On error: log the error, dismiss the active modal, show a brief toast/notification in the overlay
3. Ensure the overlay returns to a usable state (subtitle display, click-through, shortcuts all work)
4. Add a global `window.onerror` / `unhandledrejection` handler as a last-resort safety net
5. Consider a "renderer health" heartbeat from main process that can force-reload the renderer if it becomes unresponsive
## Design constraints
- Error recovery must not disrupt mpv playback
- Toast notifications should auto-dismiss and not interfere with subtitle layout
- Errors should be logged with enough context for debugging (stack trace, active modal, last subtitle state)
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Unhandled errors in modal flows are caught and do not crash the overlay.
- [ ] #2 After an error, the overlay returns to a functional state (subtitles render, shortcuts work).
- [ ] #3 A brief toast/notification informs the user that an error occurred.
- [ ] #4 Global unhandledrejection and onerror handlers are registered as safety nets.
- [ ] #5 Error details are logged with context (stack trace, active modal, subtitle state).
- [ ] #6 mpv playback is never interrupted by renderer errors.
<!-- AC:END -->

View File

@@ -0,0 +1,46 @@
---
id: TASK-38
title: Add user-friendly config validation errors on startup
status: To Do
assignee: []
created_date: '2026-02-14 02:02'
labels:
- config
- developer-experience
- error-handling
dependencies: []
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Improve config validation to surface clear, actionable error messages at startup when the user's config file has invalid values, missing required fields, or type mismatches.
## Motivation
The project has a config schema with validation, but invalid configs (wrong types, unknown keys after an upgrade, deprecated fields) can cause cryptic failures deep in service initialization rather than being caught and reported clearly at launch.
## Scope
1. Validate the full config against the schema at startup, before any services initialize
2. Collect all validation errors (don't fail on the first one) and present them as a summary
3. Show the specific field path, expected type, and actual value for each error
4. For deprecated or renamed fields, suggest the correct field name
5. Optionally show validation errors in a startup dialog (Electron dialog) rather than just console output
6. Allow the app to start with defaults for non-critical invalid fields, warning the user about the fallback
## Design constraints
- Must not block startup for non-critical warnings (e.g., unknown extra keys)
- Critical errors (e.g., invalid Anki field mappings) should prevent startup with a clear message
- Config file location should be shown in error output so users know what to edit
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 All config fields are validated against the schema before service initialization.
- [ ] #2 Validation errors show field path, expected type, and actual value.
- [ ] #3 Multiple errors are collected and shown together, not one at a time.
- [ ] #4 Deprecated or renamed fields produce a helpful migration suggestion.
- [ ] #5 Non-critical validation issues allow startup with defaults and a visible warning.
- [ ] #6 Critical validation failures prevent startup with a clear dialog or console message.
- [ ] #7 Config file path is shown in error output.
<!-- AC:END -->

View File

@@ -0,0 +1,54 @@
---
id: TASK-39
title: Add hot-reload for non-destructive config changes
status: To Do
assignee: []
created_date: '2026-02-14 02:04'
labels:
- config
- developer-experience
- quality-of-life
dependencies: []
priority: low
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Watch the config file for changes and apply non-destructive updates (colors, font sizes, subtitle modes, overlay opacity, keybindings) without requiring an app restart.
## Motivation
Currently all config is loaded at startup. Users tweaking visual settings (font size, colors, subtitle positioning) must restart the app after every change, which breaks their video session. Hot-reload for safe config values would dramatically improve the tuning experience.
## Scope
1. Watch the config file using `fs.watch` or similar
2. On change, re-parse and re-validate the config
3. Categorize config fields as hot-reloadable vs restart-required
4. Apply hot-reloadable changes immediately (push to renderer via IPC if needed)
5. For restart-required changes, show a notification that a restart is needed
6. Debounce file-change events (editors save multiple times rapidly)
## Hot-reloadable candidates
- Font family, size, weight, color
- Subtitle background opacity/color
- Secondary subtitle display mode
- Overlay opacity and positioning offsets
- Keybinding mappings
- AI translation provider settings
## Restart-required (NOT hot-reloadable)
- Anki field mappings (affects card creation pipeline)
- MeCab path / tokenizer settings
- MPV socket path
- Window tracker selection
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Config file changes are detected automatically via file watcher.
- [ ] #2 Hot-reloadable fields are applied immediately without restart.
- [ ] #3 Restart-required fields trigger a user-visible notification.
- [ ] #4 File change events are debounced to handle editor save patterns.
- [ ] #5 Invalid config changes are rejected with an error notification, keeping the previous valid config.
- [ ] #6 Renderer receives updated styles/settings via IPC without full page reload.
<!-- AC:END -->

View File

@@ -0,0 +1,30 @@
---
id: TASK-4
title: Improve Mermaid diagrams in docs for readability
status: Done
assignee: []
created_date: '2026-02-11 07:11'
updated_date: '2026-02-11 07:11'
labels: []
dependencies: []
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Refine Mermaid charts in documentation (primarily architecture docs) to improve readability, grouping, and label clarity without changing system behavior.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Mermaid diagrams render successfully in VitePress docs build
- [x] #2 Diagrams have clearer grouping, edge labels, and flow direction
- [x] #3 No broken markdown or Mermaid syntax in updated docs
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Improved Mermaid diagrams in docs/architecture.md by redesigning both flowcharts with clearer subgraphs, labeled edges, and consistent lifecycle/runtime separation. Verified successful rendering via `pnpm run docs:build` with no chunk-size warning regressions.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -0,0 +1,52 @@
---
id: TASK-40
title: Add vocab heatmap and comprehension dashboard for immersion sessions
status: To Do
assignee: []
created_date: '2026-02-14 02:06'
labels:
- feature
- analytics
- immersion
- visualization
dependencies:
- TASK-28
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Build a visual comprehension dashboard that renders per-video "comprehension heatmaps" showing which segments had high lookup density versus segments where the user understood everything without lookups.
## Motivation
Immersion tracking data (TASK-28) captures lookup counts, words seen, and cards mined per time segment. Visualizing this data gives learners a powerful way to see their progress: early episodes of a show will be "hot" (many lookups), while later episodes should cool down as vocabulary grows.
## Features
1. **Per-video heatmap**: A timeline bar colored by lookup density (green = understood, yellow = some lookups, red = many lookups)
2. **Cross-video trends**: Show comprehension improvement across episodes of the same series
3. **Session summary cards**: After each session, show a summary with key metrics (time watched, words seen, cards mined, comprehension estimate)
4. **Historical comparison**: Compare the same video rewatched at different dates
5. **Export**: Generate a shareable image or markdown summary
## Data sources
- `imm_session_telemetry` (lookup_count, words_seen per sample interval)
- `imm_session_events` (individual lookup events with timestamps)
- `imm_daily_rollups` / `imm_monthly_rollups` for trend data
## Implementation notes
- Could be rendered in a dedicated Electron window or an overlay modal
- Use canvas or SVG for the heatmap visualization
- Consider a simple HTML report that can be opened in a browser for sharing
- Heatmap granularity should match telemetry sample interval
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Per-video heatmap renders a timeline colored by lookup density.
- [ ] #2 Cross-video trend view shows comprehension change across episodes.
- [ ] #3 Session summary displays time watched, words seen, cards mined, and comprehension estimate.
- [ ] #4 Dashboard data is sourced from TASK-28 immersion tracking tables.
- [ ] #5 Visualization is exportable as an image or shareable format.
- [ ] #6 Dashboard does not block or slow down active mining sessions.
<!-- AC:END -->

View File

@@ -0,0 +1,53 @@
---
id: TASK-41
title: Add real-time sentence difficulty scoring with auto-pause on hard lines
status: To Do
assignee: []
created_date: '2026-02-14 02:09'
labels:
- feature
- nlp
- immersion
- subtitle
dependencies:
- TASK-23
- TASK-25
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Compute a real-time difficulty score for each subtitle line using JLPT level data (TASK-23) and frequency dictionary data (TASK-25), and use this score to drive smart playback features.
## Motivation
Learners at different levels have different needs. N4 learners want to pause on N2+ lines; advanced learners want to skip easy content. A per-line difficulty score enables intelligent playback that adapts to the learner's level.
## Features
1. **Per-line difficulty score**: Combine JLPT levels and frequency ranks of tokens to produce a composite difficulty score (e.g., 1-5 scale or JLPT-equivalent label)
2. **Visual difficulty indicator**: Subtle color/icon on each subtitle line indicating difficulty
3. **Auto-pause on difficult lines**: Configurable threshold — pause playback when a line exceeds the user's set difficulty level
4. **Per-episode difficulty rating**: Average difficulty across all lines, shown in the episode browser (TASK-34)
5. **Difficulty trend within a video**: Show whether difficulty increases/decreases over the episode (useful for detecting climax scenes with complex dialogue)
## Scoring algorithm (suggested)
- For each token in a line, look up JLPT level (N5=1, N1=5) and frequency rank
- Weight unknown words (not in Anki known-word cache from TASK-24) more heavily
- Composite score = weighted average of token difficulties, with bonus for line length and grammar complexity
- Configurable weights so users can tune sensitivity
## Design constraints
- Scoring must run synchronously during subtitle rendering without perceptible latency
- Score computation should be cached per subtitle line (lines repeat on seeks/replays)
- Auto-pause should be debounced to avoid rapid pause/unpause on sequential hard lines
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Each subtitle line receives a difficulty score based on JLPT and frequency data.
- [ ] #2 A visual indicator shows per-line difficulty in the overlay.
- [ ] #3 Auto-pause triggers when a line exceeds the user's configured difficulty threshold.
- [ ] #4 Difficulty scoring does not add perceptible latency to subtitle rendering.
- [ ] #5 Per-episode average difficulty is available for the episode browser.
- [ ] #6 Scoring weights are configurable in settings.
<!-- AC:END -->

View File

@@ -0,0 +1,55 @@
---
id: TASK-42
title: Add spaced repetition video clip review mode for mined Anki cards
status: To Do
assignee: []
created_date: '2026-02-14 02:11'
labels:
- feature
- anki
- immersion
- review
dependencies: []
priority: low
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Implement a "review mode" that replays short video clips from previously mined Anki cards, allowing users to review vocabulary in its original immersion context rather than as isolated text cards.
## Motivation
Traditional Anki review shows a sentence on a card. But the original context — hearing the word spoken, seeing the scene, feeling the emotion — is far more memorable. SubMiner already captures audio clips and screenshots for cards. This feature uses that data to create a video-based review experience.
## Workflow
1. User activates review mode (shortcut or menu)
2. SubMiner queries AnkiConnect for due/new cards from a configured deck
3. For each card, SubMiner locates the source video and timestamp (stored in card metadata)
4. Plays a 3-8 second clip around the mined sentence with subtitles hidden
5. User tries to recall the target word/meaning
6. On reveal: show the subtitle, highlight the target word, display the Anki card fields
7. User grades the card (easy/good/hard/again) via shortcuts, fed back to Anki via AnkiConnect
## Technical considerations
- Requires source video files to still be accessible (local path stored in card)
- Falls back gracefully if video is unavailable (show audio clip + screenshot instead)
- MPV can seek to timestamp and play a range (`--start`, `--end` flags or `loadfile` with options)
- Card metadata needs a consistent format for source video path and timestamp (may need to standardize the field SubMiner writes)
- Consider a playlist mode that queues multiple clips for batch review
## Design constraints
- Must work with existing Anki card format (no retroactive card changes required)
- Graceful degradation when source video is missing
- Review session should be pausable and resumable
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Review mode queries AnkiConnect for due cards and locates source video + timestamp.
- [ ] #2 Video clips play with subtitles hidden, then revealed on user action.
- [ ] #3 Target word is highlighted in the revealed subtitle.
- [ ] #4 User can grade cards (easy/good/hard/again) via keyboard shortcuts.
- [ ] #5 Grades are sent back to Anki via AnkiConnect.
- [ ] #6 Graceful fallback when source video file is unavailable (audio + screenshot).
- [ ] #7 Review session can be paused and resumed.
<!-- AC:END -->

View File

@@ -0,0 +1,51 @@
---
id: TASK-43
title: Add community subtitle timing database for shared sync corrections
status: To Do
assignee: []
created_date: '2026-02-14 02:13'
labels:
- feature
- community
- subtitles
- sync
dependencies: []
priority: low
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Allow users to share their subtitle timing corrections to a community database, so other users watching the same video file get pre-synced subtitles automatically.
## Motivation
Subtitle synchronization (alass/ffsubsync) is one of the most friction-heavy steps in the mining workflow. Users spend time syncing subtitles that someone else has already synced for the exact same video. A shared database of timing corrections keyed by video file hash would eliminate redundant work.
## Design
1. **Video identification**: Use a partial file hash (first + last N bytes, or a media fingerprint) to identify video files without uploading content
2. **Timing data**: Store the timing offset/warp parameters produced by alass/ffsubsync, not the full subtitle file
3. **Upload flow**: After a successful sync, offer to share the timing correction (opt-in)
4. **Download flow**: Before syncing, check the community database for existing corrections for the current video hash
5. **Trust model**: Simple upvote/downvote on corrections; show number of users who confirmed a correction works
## Technical considerations
- Backend could be a simple REST API with a lightweight database (or even a GitHub-hosted JSON/SQLite file for v1)
- Privacy: only file hashes and timing parameters are shared, never video content or personal data
- Subtitle source (jimaku entry ID) can serve as an additional matching key
- Rate limiting and abuse prevention needed for public API
- Could integrate with existing jimaku modal flow
## Phasing
- v1: Local export/import of timing corrections (share as files)
- v2: Optional cloud sync with community database
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Video files are identified by content hash without uploading video data.
- [ ] #2 Timing corrections (offset/warp parameters) can be exported and shared.
- [ ] #3 Before syncing, the app checks for existing community corrections for the current video.
- [ ] #4 Upload of timing data is opt-in with clear privacy disclosure.
- [ ] #5 Downloaded corrections are applied automatically or with one-click confirmation.
- [ ] #6 Trust signal (confirmation count) is shown for community corrections.
<!-- AC:END -->

View File

@@ -0,0 +1,48 @@
---
id: TASK-44
title: Add multi-window subtitle mode for secondary monitor display
status: To Do
assignee: []
created_date: '2026-02-14 02:15'
labels:
- feature
- renderer
- multi-monitor
- quality-of-life
dependencies: []
priority: low
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Support rendering the subtitle overlay on a secondary display at a larger, more readable size while the video plays full-screen on the primary monitor.
## Motivation
For study-focused sessions, reading subtitles overlaid on video at normal size can cause eye strain. Users with multiple monitors would benefit from a dedicated subtitle display — larger font, clean background, with full tokenization and click-to-lookup functionality — while the video plays unobstructed on the main screen.
## Features
1. **Detached subtitle window**: A second Electron BrowserWindow that mirrors the current subtitle content
2. **Configurable placement**: User selects which monitor and position for the subtitle window
3. **Independent styling**: The detached window can have different font size, background opacity, and layout than the overlay
4. **Synchronized content**: Both windows show the same subtitle in real-time
5. **Lookup support**: Yomitan click-to-lookup works in the detached window
6. **Toggle**: Shortcut to quickly enable/disable the secondary display
## Technical considerations
- Electron supports multi-monitor via `screen.getAllDisplays()` and `BrowserWindow` bounds
- The detached window should receive subtitle updates via IPC from the main process (same path as the overlay)
- Overlay on the primary monitor could optionally be hidden when the detached window is active
- Window should remember its position across sessions (persist to config or state file)
- Consider whether the detached window should be always-on-top or normal
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 A detached subtitle window can be opened on a secondary monitor.
- [ ] #2 Subtitle content is synchronized in real-time between overlay and detached window.
- [ ] #3 Detached window has independently configurable font size and styling.
- [ ] #4 Yomitan word lookup works in the detached window.
- [ ] #5 Shortcut toggles the detached window on/off.
- [ ] #6 Window position is persisted across sessions.
<!-- AC:END -->

View File

@@ -0,0 +1,54 @@
---
id: TASK-45
title: Add exportable session highlights and mining summary reports
status: To Do
assignee: []
created_date: '2026-02-14 02:17'
labels:
- feature
- analytics
- immersion
- social
dependencies:
- TASK-28
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
After a mining session, generate a visual summary report with key metrics that can be exported as an image or markdown for sharing on language learning communities.
## Motivation
Language learners love tracking and sharing their progress. A polished session summary creates a natural sharing loop and helps users stay motivated. Communities like r/LearnJapanese, TheMoeWay Discord, and Refold regularly share immersion stats.
## Summary contents
1. **Session metrics**: Duration watched, active time, lines seen, words encountered, cards mined
2. **Efficiency stats**: Cards per hour, words per minute, lookup hit rate
3. **Top looked-up words**: The N most-looked-up words in the session
4. **Streak data**: Current daily immersion streak, total immersion hours
5. **Word cloud**: Visual word cloud of looked-up or mined vocabulary
6. **Video context**: Show/episode name, thumbnail
## Export formats
- **Image**: Rendered card-style graphic (PNG) suitable for social media sharing
- **Markdown**: Text summary for pasting into Discord/forums
- **Clipboard**: One-click copy of the summary
## Implementation notes
- Generate the image using HTML-to-canvas in the renderer, or a Node canvas library
- Data sourced from TASK-28 immersion tracking tables
- Summary could be shown automatically at session end (when mpv closes) or on demand via shortcut
- Consider a cumulative weekly/monthly summary in addition to per-session
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Session summary shows duration, lines seen, words encountered, and cards mined.
- [ ] #2 Top looked-up words are listed in the summary.
- [ ] #3 Summary is exportable as PNG image.
- [ ] #4 Summary is exportable as markdown text.
- [ ] #5 One-click copy to clipboard is supported.
- [ ] #6 Summary can be triggered on demand via shortcut or shown automatically at session end.
- [ ] #7 Data is sourced from immersion tracking (TASK-28).
<!-- AC:END -->

View File

@@ -0,0 +1,56 @@
---
id: TASK-46
title: >-
Add listening-only mode with on-demand subtitle reveal for comprehension
training
status: To Do
assignee: []
created_date: '2026-02-14 02:19'
labels:
- feature
- immersion
- subtitle
- listening
dependencies: []
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Implement a listening-only mode that hides subtitles by default and only reveals them on demand (click, tap, or shortcut), tracking how many lines the user understood without looking.
## Motivation
Subtitle dependency is a common trap for language learners. Users watch hundreds of hours with subtitles but struggle when subtitles are removed. A listening-only mode provides a structured way to wean off subtitles: watch without them, check when needed, and track comprehension over time.
## Features
1. **Hidden-by-default subtitles**: Subtitles are loaded and tracked but not displayed
2. **On-demand reveal**: Press a key or click to reveal the current subtitle line briefly (3-5 seconds, then auto-hides)
3. **Comprehension tracking**: Track reveal rate (lines revealed / total lines) as a listening comprehension metric
4. **Difficulty-aware reveal**: Optionally auto-reveal lines above a difficulty threshold (pairs with sentence difficulty scoring feature)
5. **Session stats**: At session end, show listening comprehension percentage
6. **Progressive mode**: Start with subtitles visible, then fade them out after N minutes (configurable ramp)
## Technical considerations
- Subtitle timing is still tracked (for Anki card creation if user mines a revealed line)
- MPV's native subtitles should be hidden; SubMiner handles visibility
- Reveal animation should be smooth (fade in/out, not jarring)
- Mining workflow should still work: reveal → click word → Yomitan → Anki
- Comprehension data feeds into TASK-28 immersion tracking if available
## Design constraints
- Must not break existing subtitle display modes
- Listening mode should be a toggle (shortcut to enter/exit)
- Reveal count and comprehension rate should persist with session data
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Listening mode hides subtitles by default while continuing to track timing.
- [ ] #2 Shortcut or click reveals the current subtitle briefly (configurable duration).
- [ ] #3 Reveal rate (lines revealed / total lines) is tracked per session.
- [ ] #4 Mining workflow works on revealed lines (click word → Yomitan → Anki).
- [ ] #5 Session end shows listening comprehension percentage.
- [ ] #6 Mode is toggleable via shortcut without restarting.
- [ ] #7 Subtitle reveal has smooth fade in/out animation.
<!-- AC:END -->

View File

@@ -0,0 +1,59 @@
---
id: TASK-47
title: Add Anki card quality analytics with retention correlation insights
status: To Do
assignee: []
created_date: '2026-02-14 02:21'
labels:
- feature
- anki
- analytics
- immersion
dependencies:
- TASK-28
priority: low
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Analyze existing Anki cards created by SubMiner to identify which card characteristics correlate with better retention, helping users understand what makes a "good" mining card for them personally.
## Motivation
Not all mined cards are equal. Some are remembered easily; others become leeches. By analyzing retention data from Anki alongside card characteristics (sentence length, word frequency, JLPT level, context richness), SubMiner can provide personalized insights about optimal mining strategies.
## Features
1. **Card retention analysis**: Query Anki for review history of SubMiner-created cards, compute retention rates
2. **Characteristic correlation**: Correlate retention with:
- Sentence length (words/characters)
- Target word frequency rank
- Target word JLPT level
- Number of unknown words in the sentence (i+N analysis)
- Whether audio/screenshot was included
- Source media genre/type
3. **Insights dashboard**: Show actionable insights like "Your best-retained cards have 8-15 words and 1-2 unknown words"
4. **Mining recommendations**: Real-time suggestions during mining — "This sentence has 4 unknown words; consider a simpler example"
5. **Leech prediction**: Flag newly created cards that match the profile of past leeches
## Technical considerations
- Requires querying Anki review history via AnkiConnect (cardInfo, getReviewsOfCards)
- Analysis can run as a background task during idle time
- Results should be cached locally (SQLite via TASK-28 or separate store)
- Privacy-sensitive: all analysis is local, no data leaves the machine
- Consider batch analysis (run nightly or on demand) vs real-time
## Design constraints
- Must not slow down the mining workflow
- Insights should be actionable, not just statistical
- Analysis should work with existing card format (no retroactive changes needed)
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Retention rates are computed for SubMiner-created Anki cards via AnkiConnect.
- [ ] #2 Correlations between card characteristics and retention are computed and displayed.
- [ ] #3 Insights dashboard shows actionable recommendations (optimal sentence length, i+N target).
- [ ] #4 Real-time mining suggestions appear when creating cards with suboptimal characteristics.
- [ ] #5 Analysis runs in background without impacting mining performance.
- [ ] #6 All analysis is local — no data sent externally.
<!-- AC:END -->

View File

@@ -0,0 +1,99 @@
---
id: TASK-5
title: Eliminate type duplication between renderer.ts and types.ts
status: Done
assignee:
- codex
created_date: '2026-02-11 08:20'
updated_date: '2026-02-11 17:46'
labels:
- refactor
- types
- renderer
milestone: Codebase Clarity & Composability
dependencies: []
references:
- src/renderer/renderer.ts
- src/types.ts
- src/main.ts
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
renderer.ts locally redefines 20+ interfaces/types that already exist in types.ts: MergedToken, SubtitleData, MpvSubtitleRenderMetrics, Keybinding, SubtitlePosition, SecondarySubMode, all Jimaku/Kiku types, RuntimeOption types, SubsyncSourceTrack, SubsyncManualPayload, etc.
This creates divergence risk — changes in types.ts don't automatically propagate to the renderer's local copies.
Additionally, `DEFAULT_MPV_SUBTITLE_RENDER_METRICS` and `sanitizeMpvSubtitleRenderMetrics()` exist only in renderer.ts despite being shared concerns (main.ts also has DEFAULT_MPV_SUBTITLE_RENDER_METRICS).
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 All shared types are imported from types.ts — no local redefinitions in renderer.ts
- [x] #2 DEFAULT_MPV_SUBTITLE_RENDER_METRICS lives in one canonical location (types.ts or a shared module)
- [x] #3 sanitizeMpvSubtitleRenderMetrics moved to a shared module importable by both main and renderer
- [x] #4 TypeScript compiles cleanly with no type errors
- [x] #5 Renderer-only types (ChordAction, KikuModalStep, KikuPreviewMode) can stay local
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1) Audit `src/renderer/renderer.ts`, `src/types.ts`, and `src/main.ts` to identify every duplicated type and both subtitle-metrics helpers/constants.
2) Remove duplicated shared type declarations from `renderer.ts` and replace them with direct imports from `types.ts`; keep renderer-only types (`ChordAction`, `KikuModalStep`, `KikuPreviewMode`) local.
3) Create a single canonical home for `DEFAULT_MPV_SUBTITLE_RENDER_METRICS` in a shared location and update all call sites to import it from that canonical module.
4) Move `sanitizeMpvSubtitleRenderMetrics` to a shared module importable by both main and renderer, then switch both files to consume that shared implementation.
5) Run TypeScript compile/check, fix any fallout, and verify no local shared-type redefinitions remain in `renderer.ts`.
6) Update task notes and check off acceptance criteria as each item is validated.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Replaced renderer-local shared type declarations with `import type` from `src/types.ts`; only `KikuModalStep`, `KikuPreviewMode`, and `ChordAction` remain local in renderer.
Moved canonical MPV subtitle metrics defaults to `src/core/services/mpv-render-metrics-service.ts` as `DEFAULT_MPV_SUBTITLE_RENDER_METRICS` and switched `main.ts` to consume it.
Added shared `sanitizeMpvSubtitleRenderMetrics` export in `src/core/services/mpv-render-metrics-service.ts` and re-exported it from `src/core/services/index.ts`.
Removed renderer-local `DEFAULT_MPV_SUBTITLE_RENDER_METRICS`, `coerceFiniteNumber`, and `sanitizeMpvSubtitleRenderMetrics`; renderer now consumes full sanitized metrics from main via IPC and keeps nullable local state until startup metrics are loaded.
Validation: `pnpm run build` passed; `node --test dist/core/services/mpv-render-metrics-service.test.js` passed.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Implemented TASK-5 by eliminating duplicate shared type/model definitions from the renderer and centralizing MPV subtitle render metrics primitives.
What changed:
- `src/renderer/renderer.ts`
- Removed local redefinitions of shared interfaces/types (subtitle, keybinding, Jimaku, Kiku, runtime options, subsync, render metrics).
- Added `import type` usage from `src/types.ts` for shared contracts.
- Kept renderer-only local types (`KikuModalStep`, `KikuPreviewMode`, `ChordAction`).
- Removed renderer-local metrics default/sanitization helpers and switched invisible overlay resize behavior to rely on already-synced metrics state from main.
- `src/core/services/mpv-render-metrics-service.ts`
- Added canonical `DEFAULT_MPV_SUBTITLE_RENDER_METRICS` export.
- Added shared `sanitizeMpvSubtitleRenderMetrics` export.
- `src/core/services/index.ts`
- Re-exported `DEFAULT_MPV_SUBTITLE_RENDER_METRICS` and `sanitizeMpvSubtitleRenderMetrics`.
- `src/main.ts`
- Removed local default metrics constant and imported the canonical default from core services.
- `src/core/services/mpv-render-metrics-service.test.ts`
- Updated base fixture to derive from canonical default metrics constant.
Why:
- Prevent type drift between renderer and shared contracts.
- Establish a single source of truth for MPV subtitle render metric defaults and sanitization utilities.
Validation:
- `pnpm run build`
- `node --test dist/core/services/mpv-render-metrics-service.test.js`
Result:
- Shared renderer types now come from `types.ts`.
- MPV subtitle render metrics defaults are canonicalized in one shared module.
- TypeScript compiles cleanly and relevant metrics service tests pass.
<!-- SECTION:FINAL_SUMMARY:END -->

Some files were not shown because too many files have changed in this diff Show More