Compare commits

..

65 Commits

Author SHA1 Message Date
a954f62f55 Decouple stats daemon and preserve final mine OSD status
- Run `subminer stats -b` as a dedicated daemon process, independent from the overlay app
- Stop Anki progress spinner before showing final `✓`/`x` mine result so it is not overwritten
- Keep grammar/noise subtitle tokens hoverable while stripping annotation metadata
2026-03-18 23:49:27 -07:00
4d96ebf5c0 fix: reduce prefetched subtitle annotation delay 2026-03-18 23:47:33 -07:00
7a0d7a488b docs: redesign README for cleaner layout and scannability
- Condense features into bold-label paragraphs instead of H3 subsections
- Collapse install instructions into <details> sections
- Remove redundant screenshots (annotations-key, stats-vocabulary)
- Add AUR version badge
- Merge first-launch and setup steps into a single paragraph
- Add horizontal rule dividers between major sections
2026-03-18 23:35:17 -07:00
f916b65d7f fix: sync texthooker-ui annotation overrides 2026-03-18 19:32:51 -07:00
36627bf87d fix(anki): avoid unnecessary known-word cache restarts 2026-03-18 19:29:47 -07:00
ad1f66a842 feat: sync animated anki images to sentence audio 2026-03-18 19:21:12 -07:00
f4cce31d4a fix: align texthooker and stats formatting with CI expectations 2026-03-18 19:01:29 -07:00
ec56970646 fix(ci): install stats deps in release builds 2026-03-18 02:37:58 -07:00
48f10dbb03 chore(backlog): maintain task backlog and add changelog fragments
- Move completed tasks (85, 117, 118, 155) to backlog/completed/
- Delete superseded task files (166 verification, 172 drilldown)
- Add stats dashboard milestone m-1
- Add new tasks (190, 194)
- Update task metadata across remaining backlog items
- Add changelog fragments for stats, mpv args, and subtitle filtering
2026-03-18 02:25:07 -07:00
1cb129b0b7 chore: update README, gitignore, and add CLAUDE.md
- Refresh README with feature overview and screenshot embeds
- Add .superpowers/ and clean up duplicate gitignore entries
- Add CLAUDE.md project instructions
- Remove stale release/release-notes.md
2026-03-18 02:24:57 -07:00
af1505fbe6 docs: update config examples, docs site, and add screenshots
- Update config examples with word field, retention, and stats options
- Add immersion tracking documentation for retention presets
- Add Anki integration and configuration docs updates
- Add stats dashboard screenshots
2026-03-18 02:24:46 -07:00
97126caf4e feat(stats): add note ID resolution and session event handling improvements
- Add note ID resolution through merge redirects in stats API
- Build Anki note previews using configured field names
- Add session event helpers for merged note dedup and stable request keys
- Refactor SessionDetail to prevent redundant note info requests
- Add session event popover and API client tests
2026-03-18 02:24:38 -07:00
a0015dc75c feat: add configurable Anki word field with note ID merge tracking
- Extract word field config into reusable anki-field-config module
- Add ankiConnect.fields.word config option (default: "Expression")
- Replace hardcoded "Expression" field references across Anki integration
- Add note ID redirect tracking for merged/moved cards
- Support legacy ankiConnect.wordField migration path
2026-03-18 02:24:26 -07:00
61e1621137 perf: split stats app bundles by route 2026-03-18 00:05:51 -07:00
a5b1c0509d fix(stats): align session event popovers with chart plot area 2026-03-17 23:56:58 -07:00
e694963426 fix(stats): address PR 19 review follow-ups 2026-03-17 23:56:42 -07:00
a69254f976 feat(stats): show seek length in session event tooltip 2026-03-17 23:38:45 -07:00
a1348cf8e4 chore(backlog): add m-1 milestone for remaining stats fixes 2026-03-17 23:38:33 -07:00
f9b582582b fix(stats): load full session timelines by default 2026-03-17 22:37:34 -07:00
8f39416ff5 fix(stats): use yomitan tokens for subtitle counts 2026-03-17 22:33:08 -07:00
ecb41a490b feat(launcher): add mpv args passthrough 2026-03-17 21:51:52 -07:00
b061b265c2 chore(vendor): bump subminer-yomitan 2026-03-17 20:12:42 -07:00
f2b3af17d7 docs: update docs, add backlog tasks and change notes
Update configuration, immersion tracking, and mining workflow docs.
Add backlog tasks for upcoming work items and change notes for recent
features and fixes.
2026-03-17 20:12:42 -07:00
5698121996 chore: minor fixes and cleanup across services and renderer 2026-03-17 20:12:41 -07:00
f8e2ae4887 feat: overhaul stats dashboard with navigation, trends, and anime views
Add navigation state machine for tab/detail routing, anime overview
stats with Yomitan lookup rates, session word count accuracy fixes,
vocabulary tab hook order fix, simplified trends data fetching from
backend-aggregated endpoints, and improved session detail charts.
2026-03-17 20:12:41 -07:00
08a5401a7d feat: add background stats server daemon lifecycle
Implement `subminer stats -b` to start a background stats daemon and
`subminer stats -s` to stop it, with PID-based process lifecycle
management, single-instance lock bypass for daemon mode, and automatic
reuse of running daemon instances.
2026-03-17 20:12:41 -07:00
55ee12e87f feat: refactor immersion tracker queries and session word tracking
Add comprehensive query helpers for session deletion with word aggregate
refresh, known-words-per-session timeline, anime-level word summaries,
and trends dashboard aggregation. Track yomitanLookupCount in session
metrics and support bulk session operations.
2026-03-17 20:12:41 -07:00
a5a6426fe1 feat: add mark-as-watched keybinding and Yomitan lookup tracking
Add configurable keybinding to mark the current video as watched with
IPC plumbing between renderer and main process. Add event listener
infrastructure for tracking Yomitan dictionary lookups per session.
2026-03-17 20:12:41 -07:00
75f2c212c7 refactor: centralize watch threshold constant
Extract DEFAULT_MIN_WATCH_RATIO (0.85) into src/shared/watch-threshold.ts
to replace the hardcoded ANILIST_UPDATE_MIN_WATCH_RATIO in main.ts.
2026-03-17 20:05:08 -07:00
3dd337f518 update backlog tasks 2026-03-17 20:05:08 -07:00
94ec28b48c refactor: isolate character dictionary completion handling 2026-03-17 20:05:08 -07:00
078792e0b2 docs: refresh immersion and stats documentation 2026-03-17 20:05:08 -07:00
390ae1b2f2 feat: optimize stats dashboard data and components 2026-03-17 20:05:08 -07:00
11710f20db feat: stabilize startup sync and overlay/runtime paths 2026-03-17 20:05:08 -07:00
de574c04bd Add isolated typecheck test for get_frequency script
- add `scripts/get_frequency.test.ts` to verify `scripts/get_frequency.ts` typechecks alone
- remove duplicate `yomitanSession` property from runtime state/interface
2026-03-17 20:05:08 -07:00
a9e33618e7 chore: apply remaining workspace formatting and updates 2026-03-17 20:05:08 -07:00
77c35c770d chore: add stats lint/check wiring for CI 2026-03-17 20:05:08 -07:00
64e9821e7a chore(backlog): sync task metadata and archives 2026-03-17 20:05:08 -07:00
5c529802c6 fix(stats): restore cross-anime words table 2026-03-17 20:05:08 -07:00
8123a145c0 fix(plugin): add lowercase linux binary fallbacks 2026-03-17 20:05:07 -07:00
659118c20c docs: document stats page mining, word exclusions, and vocabulary UX improvements 2026-03-17 20:05:07 -07:00
929159bba5 test(renderer): verify excluded interjections remain visible as non-interactive text 2026-03-17 20:05:07 -07:00
a317019bb9 feat(tokenizer): exclude interjections and sound effects from subtitle annotations
- Filter out 感動詞 (interjection) POS1 tokens from annotation payloads
- Exclude common interjection terms (ああ, ええ, はあ, etc.)
- Exclude reduplicated kana SFX with optional trailing と
- shouldExcludeTokenFromSubtitleAnnotations checks both POS1 and term patterns
- filterSubtitleAnnotationTokens applied after annotation stage
2026-03-17 20:05:07 -07:00
5767667d51 feat(stats): add mine card from stats page with Yomitan bridge
- POST /api/stats/mine-card endpoint with mode=word|sentence|audio
- mode=word: creates full Yomitan card (definition/reading/pitch) via hidden search page bridge
- mode=sentence/audio: creates card directly with Lapis/Kiku flags
- Audio + image generated in parallel from source video via ffmpeg
- Respects all AnkiConnect config (AVIF, static, field mappings, metadata pattern)
- addYomitanNoteViaSearch calls window.__subminerAddNote exposed by Yomitan fork
- Syncs AnkiConnect URL to Yomitan before each word mine
2026-03-17 20:05:07 -07:00
a1f30fd482 feat(tracking): store secondary subtitle text and source path in occurrence data
- Add secondary_text column to imm_subtitle_lines with migration
- Pass currentSecondarySubText through recordSubtitleLine flow
- Include secondaryText and sourcePath in word/kanji occurrence queries
- Update all type interfaces (backend + frontend)
2026-03-17 20:05:07 -07:00
5a30446809 feat(stats): add click handler to bar charts for word detail navigation 2026-03-17 20:05:07 -07:00
6634255f43 feat(stats): fix truncated readings and improve word detail UX
- fullReading() reconstructs full word reading from headword + partial stored reading
- FrequencyRankTable always shows reading for every row
- Word highlighted in example sentences with underline style
- Bar chart clicks open word detail panel
2026-03-17 20:05:07 -07:00
a3ed8dcf3d feat(stats): add word exclusion list for vocabulary tab
- useExcludedWords hook manages excluded words in localStorage
- ExclusionManager modal for viewing/removing excluded words
- Exclusion button on WordDetailPanel header
- Filtered words affect stat cards, charts, frequency table, and word list
2026-03-17 20:05:07 -07:00
92c1557e46 refactor: split known words config from n-plus-one 2026-03-17 20:05:07 -07:00
04682a02cc feat: improve stats dashboard and annotation settings 2026-03-17 20:05:07 -07:00
650e95cdc3 Feature/renderer performance (#24) 2026-03-17 20:05:07 -07:00
46fbea902a Harden stats APIs and fix Electron Yomitan debug runtime
- Validate stats session IDs/limits and add AnkiConnect request timeouts
- Stabilize stats window/runtime lifecycle and tighten window security defaults
- Fix Electron CLI debug startup by unsetting `ELECTRON_RUN_AS_NODE` and wiring Yomitan session state
- Expand regression coverage for tracker queries/events ordering and session aggregates
- Update docs for stats dashboard usage and Yomitan lookup troubleshooting
2026-03-17 20:05:07 -07:00
93811ebfde fix(launcher): default stats cleanup to vocab mode 2026-03-17 20:05:07 -07:00
74544d79a7 docs: update stats dashboard docs, config, and releasing checklist
- Update dashboard tab descriptions to include Anime tab and richer session timelines
- Add autoOpenBrowser config option to stats section
- Add subminer stats cleanup command to changelog fragment
- Expand releasing checklist with doc verification, changelog lint, and build gates
2026-03-17 20:05:07 -07:00
536f0a1315 feat(stats): redesign session timeline and clean up vocabulary tab
- Replace cumulative line chart with activity-focused area chart showing per-interval new words
- Add total words as a blue line on a secondary right Y-axis
- Add pause shaded regions, seek markers, and card mined markers with numeric x-axis for reliable rendering
- Add clickable header logo with proper aspect ratio
- Remove unused "Hide particles & single kana" checkbox from vocabulary tab
2026-03-17 20:05:07 -07:00
ff2d9141bc feat(stats): add episodes completed and anime completed to tracking snapshot
- Query watched videos count and anime with all episodes watched
- Display in overview tracking snapshot
- Remove efficiency section from trends
2026-03-17 20:05:07 -07:00
249a7cada8 chore: remove implementation plan documents 2026-03-17 20:05:07 -07:00
9530445a95 feat: add AniList rate limiter and remaining backlog tasks 2026-03-17 20:05:07 -07:00
2d87dae6cc docs: update documentation site for stats dashboard and immersion tracking 2026-03-17 20:05:07 -07:00
0f44107beb feat(stats): build anime-centric stats dashboard frontend
5-tab React dashboard with Catppuccin Mocha theme:
- Overview: hero stats, streak calendar, watch time chart, recent sessions
- Anime: grid with cover art, episode list with completion %, detail view
- Trends: 15 charts across Activity, Efficiency, Anime, and Patterns
- Vocabulary: POS-filtered word/kanji lists with detail panels
- Sessions: expandable session history with event timeline

Features:
- Cross-tab navigation (anime <-> vocabulary)
- Global word detail panel overlay
- Expandable episode detail with Anki card links (Expression field)
- Per-anime multi-line trend charts
- Watch time by day-of-week and hour-of-day
- Collapsible sections with accessibility (aria-expanded)
- Card size selector for anime grid
- Cover art caching via AniList
- HTTP API client with file:// protocol fallback for Electron overlay
2026-03-17 20:05:07 -07:00
950263bd66 feat(stats): add launcher stats command and build integration
- Launcher stats subcommand with cleanup mode
- Stats frontend build integrated into Makefile
- CI workflow updated for stats package
- Config example updated with stats section
- mpv plugin menu entry for stats toggle
2026-03-17 20:05:07 -07:00
26fb5b4162 feat(stats): wire stats server, overlay, and CLI into main process
- Stats server auto-start on immersion tracker init
- Stats overlay toggle via keybinding and IPC
- Stats CLI command (subminer stats) with cleanup mode
- mpv plugin menu integration for stats toggle
- CLI args for --stats, --stats-cleanup, --stats-response-path
2026-03-17 20:04:40 -07:00
ffe5c6e1c6 feat(stats): add stats server, API endpoints, config, and Anki integration
- Hono HTTP server with 20+ REST endpoints for stats data
- Stats overlay BrowserWindow with toggle keybinding
- IPC channel definitions and preload bridge
- Stats config section (toggleKey, serverPort, autoStartServer, autoOpenBrowser)
- Config resolver for stats section
- AnkiConnect proxy endpoints (guiBrowse, notesInfo)
- Note ID passthrough in card mining callback chain
- Stats CLI command with autoOpenBrowser respect
2026-03-17 20:04:40 -07:00
fe8bb167c4 feat(immersion): add anime metadata, occurrence tracking, and schema upgrades
- Add imm_anime table with AniList integration
- Add imm_subtitle_lines, imm_word_line_occurrences, imm_kanji_line_occurrences
- Add POS fields (part_of_speech, pos1, pos2, pos3) to imm_words
- Add anime metadata parsing with guessit fallback
- Add video duration tracking and watched status
- Add episode, streak, trend, and word/kanji detail queries
- Deduplicate subtitle line recording within sessions
- Pass Anki note IDs through card mining callback chain
2026-03-17 20:01:23 -07:00
cc5d270b8e docs: add stats dashboard design docs, plans, and knowledge base
- Stats dashboard redesign design and implementation plans
- Episode detail and Anki card link design
- Internal knowledge base restructure
- Backlog tasks for testing, verification, and occurrence tracking
2026-03-17 20:01:23 -07:00
816 changed files with 14694 additions and 60882 deletions

View File

@@ -1,20 +0,0 @@
{
"name": "subminer-local",
"interface": {
"displayName": "SubMiner Local"
},
"plugins": [
{
"name": "subminer-workflow",
"source": {
"source": "local",
"path": "./plugins/subminer-workflow"
},
"policy": {
"installation": "AVAILABLE",
"authentication": "ON_INSTALL"
},
"category": "Productivity"
}
]
}

View File

@@ -1,22 +1,127 @@
---
name: 'subminer-change-verification'
description: 'Compatibility shim. Canonical SubMiner change verification workflow now lives in the repo-local subminer-workflow plugin.'
name: "subminer-change-verification"
description: "Use when working in the SubMiner repo and you need to verify code changes actually work. Covers targeted regression checks during debugging and pre-handoff verification, with cheap-first lane selection for config, docs, launcher/plugin, runtime-compat, and optional real-runtime escalation."
---
# Compatibility Shim
# SubMiner Change Verification
Canonical source:
Use this skill for SubMiner code changes. Default to cheap, repo-native verification first. Escalate only when the changed behavior actually depends on Electron, mpv, overlay/window tracking, or other GUI-sensitive runtime behavior.
- `plugins/subminer-workflow/skills/subminer-change-verification/SKILL.md`
## Scripts
Canonical helper scripts:
- `scripts/classify_subminer_diff.sh`
- Emits suggested lanes and flags from explicit paths or current git changes.
- `scripts/verify_subminer_change.sh`
- Runs selected lanes, captures artifacts, and writes a compact summary.
- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh`
- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh`
If you need an explicit installed path, use the directory that contains this `SKILL.md`. The helper scripts live under:
When this shim is invoked:
```bash
export SUBMINER_VERIFY_SKILL="<path-to-skill>"
```
1. Read the canonical plugin-owned skill.
2. Follow the plugin-owned skill as the source of truth.
3. Use the wrapper scripts in this shim directory only for compatibility with existing commands, docs, and backlog history.
4. Do not duplicate workflow changes here; update the plugin-owned skill and scripts instead.
## Default workflow
1. Inspect the changed files or user-requested area.
2. Run the classifier unless you already know the right lane.
3. Run the verifier with the cheapest sufficient lane set.
4. If the classifier emits `flag:real-runtime-candidate`, do not jump straight to runtime verification. First run the non-runtime lanes.
5. Escalate to explicit `--lane real-runtime --allow-real-runtime` only when cheaper lanes cannot validate the behavior claim.
6. Return:
- verification summary
- exact commands run
- artifact paths
- skipped lanes and blockers
## Quick start
Repo-source quick start:
```bash
bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh
```
Installed-skill quick start:
```bash
bash "$SUBMINER_VERIFY_SKILL/scripts/classify_subminer_diff.sh"
```
Classify explicit files:
```bash
bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh \
launcher/main.ts \
plugin/subminer/lifecycle.lua \
src/main/runtime/mpv-client-runtime-service.ts
```
Run automatic lane selection:
```bash
bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh
```
Installed-skill form:
```bash
bash "$SUBMINER_VERIFY_SKILL/scripts/verify_subminer_change.sh"
```
Run targeted lanes:
```bash
bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh \
--lane launcher-plugin \
--lane runtime-compat
```
Dry-run to inspect planned commands and artifact layout:
```bash
bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh \
--dry-run \
launcher/main.ts \
src/main.ts
```
## Lane guidance
- `docs`
- For `docs-site/`, `docs/`, and doc-only edits.
- `config`
- For `src/config/` and config-template-sensitive edits.
- `core`
- For general source changes where `typecheck` + `test:fast` is the best cheap signal.
- `launcher-plugin`
- For `launcher/`, `plugin/subminer/`, plugin gating scripts, and wrapper/mpv routing work.
- `runtime-compat`
- For `src/main*`, runtime/composer wiring, mpv/overlay services, window trackers, and dist-sensitive behavior.
- `real-runtime`
- Only after deliberate escalation.
## Real Runtime Escalation
Escalate only when the change claim depends on actual runtime behavior, for example:
- overlay appears, hides, or tracks a real mpv window
- mpv launch flags or pause-until-ready behavior
- plugin/socket/auto-start handshake under a real player
- macOS/window-tracker/focus-sensitive behavior
If the environment cannot support authoritative runtime verification, report the blocker explicitly. Do not silently downgrade a runtime-required claim to a pass.
## Artifact contract
The verifier writes under `.tmp/skill-verification/<timestamp>/`:
- `summary.json`
- `summary.txt`
- `classification.txt`
- `env.txt`
- `lanes.txt`
- `steps.tsv`
- `steps/*.stdout.log`
- `steps/*.stderr.log`
On failure, quote the exact failing command and point at the artifact directory.

View File

@@ -1,13 +1,163 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
REPO_ROOT=$(cd "$SCRIPT_DIR/../../../.." && pwd)
TARGET="$REPO_ROOT/plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh"
usage() {
cat <<'EOF'
Usage: classify_subminer_diff.sh [path ...]
if [[ ! -x "$TARGET" ]]; then
echo "Missing canonical script: $TARGET" >&2
exit 1
Emit suggested verification lanes for explicit paths or current local git changes.
Output format:
lane:<name>
flag:<name>
reason:<text>
EOF
}
has_item() {
local needle=$1
shift || true
local item
for item in "$@"; do
if [[ "$item" == "$needle" ]]; then
return 0
fi
done
return 1
}
add_lane() {
local lane=$1
if ! has_item "$lane" "${LANES[@]:-}"; then
LANES+=("$lane")
fi
}
add_flag() {
local flag=$1
if ! has_item "$flag" "${FLAGS[@]:-}"; then
FLAGS+=("$flag")
fi
}
add_reason() {
REASONS+=("$1")
}
collect_git_paths() {
local top_level
if ! top_level=$(git rev-parse --show-toplevel 2>/dev/null); then
return 0
fi
(
cd "$top_level"
if git rev-parse --verify HEAD >/dev/null 2>&1; then
git diff --name-only --relative HEAD --
git diff --name-only --relative --cached --
else
git diff --name-only --relative --
git diff --name-only --relative --cached --
fi
git ls-files --others --exclude-standard
) | awk 'NF' | sort -u
}
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
usage
exit 0
fi
exec "$TARGET" "$@"
declare -a PATHS=()
declare -a LANES=()
declare -a FLAGS=()
declare -a REASONS=()
if [[ $# -gt 0 ]]; then
while [[ $# -gt 0 ]]; do
PATHS+=("$1")
shift
done
else
while IFS= read -r line; do
[[ -n "$line" ]] && PATHS+=("$line")
done < <(collect_git_paths)
fi
if [[ ${#PATHS[@]} -eq 0 ]]; then
add_lane "core"
add_reason "no changed paths detected -> default to core"
fi
for path in "${PATHS[@]}"; do
specialized=0
case "$path" in
docs-site/*|docs/*|changes/*|README.md)
add_lane "docs"
add_reason "$path -> docs"
specialized=1
;;
esac
case "$path" in
src/config/*|src/generate-config-example.ts|src/verify-config-example.ts|docs-site/public/config.example.jsonc|config.example.jsonc)
add_lane "config"
add_reason "$path -> config"
specialized=1
;;
esac
case "$path" in
launcher/*|plugin/subminer/*|plugin/subminer.conf|scripts/test-plugin-*|scripts/get-mpv-window-*|scripts/configure-plugin-binary-path.mjs)
add_lane "launcher-plugin"
add_reason "$path -> launcher-plugin"
add_flag "real-runtime-candidate"
add_reason "$path -> real-runtime-candidate"
specialized=1
;;
esac
case "$path" in
src/main.ts|src/main-entry.ts|src/preload.ts|src/main/*|src/core/services/mpv*|src/core/services/overlay*|src/renderer/*|src/window-trackers/*|scripts/prepare-build-assets.mjs)
add_lane "runtime-compat"
add_reason "$path -> runtime-compat"
add_flag "real-runtime-candidate"
add_reason "$path -> real-runtime-candidate"
specialized=1
;;
esac
if [[ "$specialized" == "0" ]]; then
case "$path" in
src/*|package.json|tsconfig*.json|scripts/*|Makefile)
add_lane "core"
add_reason "$path -> core"
;;
esac
fi
case "$path" in
package.json|src/main.ts|src/main-entry.ts|src/preload.ts)
add_flag "broad-impact"
add_reason "$path -> broad-impact"
;;
esac
done
if [[ ${#LANES[@]} -eq 0 ]]; then
add_lane "core"
add_reason "no lane-specific matches -> default to core"
fi
for lane in "${LANES[@]}"; do
printf 'lane:%s\n' "$lane"
done
for flag in "${FLAGS[@]}"; do
printf 'flag:%s\n' "$flag"
done
for reason in "${REASONS[@]}"; do
printf 'reason:%s\n' "$reason"
done

View File

@@ -1,13 +1,566 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
REPO_ROOT=$(cd "$SCRIPT_DIR/../../../.." && pwd)
TARGET="$REPO_ROOT/plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh"
usage() {
cat <<'EOF'
Usage: verify_subminer_change.sh [options] [path ...]
if [[ ! -x "$TARGET" ]]; then
echo "Missing canonical script: $TARGET" >&2
exit 1
Options:
--lane <name> Force a verification lane. Repeatable.
--artifact-dir <dir> Use an explicit artifact directory.
--allow-real-runtime Allow explicit real-runtime execution.
--allow-real-gui Deprecated alias for --allow-real-runtime.
--dry-run Record planned steps without executing commands.
--help Show this help text.
If no lanes are supplied, the script classifies the provided paths. If no paths are
provided, it classifies the current local git changes.
Authoritative real-runtime verification should be requested with explicit path
arguments instead of relying on inferred local git changes.
EOF
}
timestamp() {
date +%Y%m%d-%H%M%S
}
timestamp_iso() {
date -u +%Y-%m-%dT%H:%M:%SZ
}
generate_session_id() {
local tmp_dir
tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/subminer-verify-$(timestamp)-XXXXXX")
basename "$tmp_dir"
rmdir "$tmp_dir"
}
has_item() {
local needle=$1
shift || true
local item
for item in "$@"; do
if [[ "$item" == "$needle" ]]; then
return 0
fi
done
return 1
}
normalize_lane_name() {
case "$1" in
real-gui)
printf '%s' "real-runtime"
;;
*)
printf '%s' "$1"
;;
esac
}
add_lane() {
local lane
lane=$(normalize_lane_name "$1")
if ! has_item "$lane" "${SELECTED_LANES[@]:-}"; then
SELECTED_LANES+=("$lane")
fi
}
add_blocker() {
BLOCKERS+=("$1")
BLOCKED=1
}
append_step_record() {
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
"$1" "$2" "$3" "$4" "$5" "$6" "$7" "$8" >>"$STEPS_TSV"
}
record_env() {
{
printf 'repo_root=%s\n' "$REPO_ROOT"
printf 'session_id=%s\n' "$SESSION_ID"
printf 'artifact_dir=%s\n' "$ARTIFACT_DIR"
printf 'path_selection_mode=%s\n' "$PATH_SELECTION_MODE"
printf 'dry_run=%s\n' "$DRY_RUN"
printf 'allow_real_runtime=%s\n' "$ALLOW_REAL_RUNTIME"
printf 'session_home=%s\n' "$SESSION_HOME"
printf 'session_xdg_config_home=%s\n' "$SESSION_XDG_CONFIG_HOME"
printf 'session_mpv_dir=%s\n' "$SESSION_MPV_DIR"
printf 'session_logs_dir=%s\n' "$SESSION_LOGS_DIR"
printf 'session_mpv_log=%s\n' "$SESSION_MPV_LOG"
printf 'pwd=%s\n' "$(pwd)"
git rev-parse --short HEAD 2>/dev/null | sed 's/^/git_head=/' || true
git status --short 2>/dev/null || true
if [[ ${#PATH_ARGS[@]} -gt 0 ]]; then
printf 'requested_paths=\n'
printf ' %s\n' "${PATH_ARGS[@]}"
fi
} >"$ARTIFACT_DIR/env.txt"
}
run_step() {
local lane=$1
local name=$2
local command=$3
local note=${4:-}
local slug=${name//[^a-zA-Z0-9_-]/-}
local stdout_rel="steps/${slug}.stdout.log"
local stderr_rel="steps/${slug}.stderr.log"
local stdout_path="$ARTIFACT_DIR/$stdout_rel"
local stderr_path="$ARTIFACT_DIR/$stderr_rel"
local status exit_code
COMMANDS_RUN+=("$command")
printf '%s\n' "$command" >"$ARTIFACT_DIR/steps/${slug}.command.txt"
if [[ "$DRY_RUN" == "1" ]]; then
printf '[dry-run] %s\n' "$command" >"$stdout_path"
: >"$stderr_path"
status="dry-run"
exit_code=0
else
if bash -lc "cd \"$REPO_ROOT\" && $command" >"$stdout_path" 2>"$stderr_path"; then
status="passed"
exit_code=0
EXECUTED_REAL_STEPS=1
else
exit_code=$?
status="failed"
FAILED=1
fi
fi
append_step_record "$lane" "$name" "$status" "$exit_code" "$command" "$stdout_rel" "$stderr_rel" "$note"
printf '%s\t%s\t%s\n' "$lane" "$name" "$status"
if [[ "$status" == "failed" ]]; then
FAILURE_STEP="$name"
FAILURE_COMMAND="$command"
FAILURE_STDOUT="$stdout_rel"
FAILURE_STDERR="$stderr_rel"
return "$exit_code"
fi
}
record_nonpassing_step() {
local lane=$1
local name=$2
local status=$3
local note=$4
local slug=${name//[^a-zA-Z0-9_-]/-}
local stdout_rel="steps/${slug}.stdout.log"
local stderr_rel="steps/${slug}.stderr.log"
printf '%s\n' "$note" >"$ARTIFACT_DIR/$stdout_rel"
: >"$ARTIFACT_DIR/$stderr_rel"
append_step_record "$lane" "$name" "$status" "0" "" "$stdout_rel" "$stderr_rel" "$note"
printf '%s\t%s\t%s\n' "$lane" "$name" "$status"
}
record_skipped_step() {
record_nonpassing_step "$1" "$2" "skipped" "$3"
}
record_blocked_step() {
add_blocker "$3"
record_nonpassing_step "$1" "$2" "blocked" "$3"
}
record_failed_step() {
FAILED=1
FAILURE_STEP=$2
FAILURE_COMMAND=${FAILURE_COMMAND:-"(validation)"}
FAILURE_STDOUT="steps/${2//[^a-zA-Z0-9_-]/-}.stdout.log"
FAILURE_STDERR="steps/${2//[^a-zA-Z0-9_-]/-}.stderr.log"
add_blocker "$3"
record_nonpassing_step "$1" "$2" "failed" "$3"
}
find_real_runtime_helper() {
local candidate
for candidate in \
"$SCRIPT_DIR/run_real_runtime_smoke.sh" \
"$SCRIPT_DIR/run_real_mpv_smoke.sh"; do
if [[ -x "$candidate" ]]; then
printf '%s' "$candidate"
return 0
fi
done
return 1
}
acquire_real_runtime_lease() {
local lease_root="$REPO_ROOT/.tmp/skill-verification/locks"
local lease_dir="$lease_root/exclusive-real-runtime"
mkdir -p "$lease_root"
if mkdir "$lease_dir" 2>/dev/null; then
REAL_RUNTIME_LEASE_DIR="$lease_dir"
printf '%s\n' "$SESSION_ID" >"$lease_dir/session_id"
return 0
fi
local owner=""
if [[ -f "$lease_dir/session_id" ]]; then
owner=$(cat "$lease_dir/session_id")
fi
add_blocker "real-runtime lease already held${owner:+ by $owner}"
return 1
}
release_real_runtime_lease() {
if [[ -n "$REAL_RUNTIME_LEASE_DIR" && -d "$REAL_RUNTIME_LEASE_DIR" ]]; then
if [[ -f "$REAL_RUNTIME_LEASE_DIR/session_id" ]]; then
local owner
owner=$(cat "$REAL_RUNTIME_LEASE_DIR/session_id")
if [[ "$owner" != "$SESSION_ID" ]]; then
return 0
fi
fi
rm -rf "$REAL_RUNTIME_LEASE_DIR"
fi
}
compute_final_status() {
if [[ "$FAILED" == "1" ]]; then
FINAL_STATUS="failed"
elif [[ "$BLOCKED" == "1" ]]; then
FINAL_STATUS="blocked"
elif [[ "$EXECUTED_REAL_STEPS" == "1" ]]; then
FINAL_STATUS="passed"
else
FINAL_STATUS="skipped"
fi
}
write_summary_files() {
local lane_lines
lane_lines=$(printf '%s\n' "${SELECTED_LANES[@]}")
printf '%s\n' "$lane_lines" >"$ARTIFACT_DIR/lanes.txt"
printf '%s\n' "${BLOCKERS[@]}" >"$ARTIFACT_DIR/blockers.txt"
printf '%s\n' "${PATH_ARGS[@]}" >"$ARTIFACT_DIR/requested-paths.txt"
ARTIFACT_DIR_ENV="$ARTIFACT_DIR" \
SESSION_ID_ENV="$SESSION_ID" \
FINAL_STATUS_ENV="$FINAL_STATUS" \
PATH_SELECTION_MODE_ENV="$PATH_SELECTION_MODE" \
ALLOW_REAL_RUNTIME_ENV="$ALLOW_REAL_RUNTIME" \
SESSION_HOME_ENV="$SESSION_HOME" \
SESSION_XDG_CONFIG_HOME_ENV="$SESSION_XDG_CONFIG_HOME" \
SESSION_MPV_DIR_ENV="$SESSION_MPV_DIR" \
SESSION_LOGS_DIR_ENV="$SESSION_LOGS_DIR" \
SESSION_MPV_LOG_ENV="$SESSION_MPV_LOG" \
STARTED_AT_ENV="$STARTED_AT" \
FINISHED_AT_ENV="$FINISHED_AT" \
FAILED_ENV="$FAILED" \
FAILURE_COMMAND_ENV="${FAILURE_COMMAND:-}" \
FAILURE_STDOUT_ENV="${FAILURE_STDOUT:-}" \
FAILURE_STDERR_ENV="${FAILURE_STDERR:-}" \
bun -e '
const fs = require("fs");
const path = require("path");
function readLines(filePath) {
if (!fs.existsSync(filePath)) return [];
return fs.readFileSync(filePath, "utf8").split(/\r?\n/).filter(Boolean);
}
const artifactDir = process.env.ARTIFACT_DIR_ENV;
const reportsDir = path.join(artifactDir, "reports");
const lanes = readLines(path.join(artifactDir, "lanes.txt"));
const blockers = readLines(path.join(artifactDir, "blockers.txt"));
const requestedPaths = readLines(path.join(artifactDir, "requested-paths.txt"));
const steps = readLines(path.join(artifactDir, "steps.tsv")).map((line) => {
const [lane, name, status, exitCode, command, stdout, stderr, note] = line.split("\t");
return {
lane,
name,
status,
exitCode: Number(exitCode || 0),
command,
stdout,
stderr,
note,
};
});
const summary = {
sessionId: process.env.SESSION_ID_ENV || "",
artifactDir,
reportsDir,
status: process.env.FINAL_STATUS_ENV || "failed",
selectedLanes: lanes,
failed: process.env.FAILED_ENV === "1",
failure:
process.env.FAILED_ENV === "1"
? {
command: process.env.FAILURE_COMMAND_ENV || "",
stdout: process.env.FAILURE_STDOUT_ENV || "",
stderr: process.env.FAILURE_STDERR_ENV || "",
}
: null,
blockers,
pathSelectionMode: process.env.PATH_SELECTION_MODE_ENV || "git-inferred",
requestedPaths,
allowRealRuntime: process.env.ALLOW_REAL_RUNTIME_ENV === "1",
startedAt: process.env.STARTED_AT_ENV || "",
finishedAt: process.env.FINISHED_AT_ENV || "",
env: {
home: process.env.SESSION_HOME_ENV || "",
xdgConfigHome: process.env.SESSION_XDG_CONFIG_HOME_ENV || "",
mpvDir: process.env.SESSION_MPV_DIR_ENV || "",
logsDir: process.env.SESSION_LOGS_DIR_ENV || "",
mpvLog: process.env.SESSION_MPV_LOG_ENV || "",
},
steps,
};
const summaryJson = JSON.stringify(summary, null, 2) + "\n";
fs.writeFileSync(path.join(artifactDir, "summary.json"), summaryJson);
fs.writeFileSync(path.join(reportsDir, "summary.json"), summaryJson);
const lines = [];
lines.push(`session_id: ${summary.sessionId}`);
lines.push(`artifact_dir: ${artifactDir}`);
lines.push(`selected_lanes: ${lanes.join(", ") || "(none)"}`);
lines.push(`status: ${summary.status}`);
lines.push(`path_selection_mode: ${summary.pathSelectionMode}`);
if (requestedPaths.length > 0) {
lines.push(`requested_paths: ${requestedPaths.join(", ")}`);
}
if (blockers.length > 0) {
lines.push(`blockers: ${blockers.join(" | ")}`);
}
for (const step of steps) {
lines.push(`${step.lane}/${step.name}: ${step.status}`);
if (step.command) lines.push(` command: ${step.command}`);
lines.push(` stdout: ${step.stdout}`);
lines.push(` stderr: ${step.stderr}`);
if (step.note) lines.push(` note: ${step.note}`);
}
if (summary.failed) {
lines.push(`failure_command: ${process.env.FAILURE_COMMAND_ENV || ""}`);
}
const summaryText = lines.join("\n") + "\n";
fs.writeFileSync(path.join(artifactDir, "summary.txt"), summaryText);
fs.writeFileSync(path.join(reportsDir, "summary.txt"), summaryText);
'
}
cleanup() {
release_real_runtime_lease
}
CLASSIFIER_OUTPUT=""
ARTIFACT_DIR=""
ALLOW_REAL_RUNTIME=0
DRY_RUN=0
FAILED=0
BLOCKED=0
EXECUTED_REAL_STEPS=0
FINAL_STATUS=""
FAILURE_STEP=""
FAILURE_COMMAND=""
FAILURE_STDOUT=""
FAILURE_STDERR=""
REAL_RUNTIME_LEASE_DIR=""
STARTED_AT=""
FINISHED_AT=""
declare -a EXPLICIT_LANES=()
declare -a SELECTED_LANES=()
declare -a PATH_ARGS=()
declare -a COMMANDS_RUN=()
declare -a BLOCKERS=()
while [[ $# -gt 0 ]]; do
case "$1" in
--lane)
EXPLICIT_LANES+=("$(normalize_lane_name "$2")")
shift 2
;;
--artifact-dir)
ARTIFACT_DIR=$2
shift 2
;;
--allow-real-runtime|--allow-real-gui)
ALLOW_REAL_RUNTIME=1
shift
;;
--dry-run)
DRY_RUN=1
shift
;;
--help|-h)
usage
exit 0
;;
--)
shift
while [[ $# -gt 0 ]]; do
PATH_ARGS+=("$1")
shift
done
;;
*)
PATH_ARGS+=("$1")
shift
;;
esac
done
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
SESSION_ID=$(generate_session_id)
PATH_SELECTION_MODE="git-inferred"
if [[ ${#PATH_ARGS[@]} -gt 0 ]]; then
PATH_SELECTION_MODE="explicit"
fi
exec "$TARGET" "$@"
if [[ -z "$ARTIFACT_DIR" ]]; then
mkdir -p "$REPO_ROOT/.tmp/skill-verification"
ARTIFACT_DIR="$REPO_ROOT/.tmp/skill-verification/$SESSION_ID"
fi
SESSION_HOME="$ARTIFACT_DIR/home"
SESSION_XDG_CONFIG_HOME="$ARTIFACT_DIR/xdg"
SESSION_MPV_DIR="$ARTIFACT_DIR/mpv"
SESSION_LOGS_DIR="$ARTIFACT_DIR/logs"
SESSION_MPV_LOG="$SESSION_LOGS_DIR/mpv.log"
mkdir -p "$ARTIFACT_DIR/steps" "$ARTIFACT_DIR/reports" "$SESSION_HOME" "$SESSION_XDG_CONFIG_HOME" "$SESSION_MPV_DIR" "$SESSION_LOGS_DIR"
STEPS_TSV="$ARTIFACT_DIR/steps.tsv"
: >"$STEPS_TSV"
trap cleanup EXIT
STARTED_AT=$(timestamp_iso)
if [[ ${#EXPLICIT_LANES[@]} -gt 0 ]]; then
local_lane=""
for local_lane in "${EXPLICIT_LANES[@]}"; do
add_lane "$local_lane"
done
printf 'reason:explicit lanes supplied\n' >"$ARTIFACT_DIR/classification.txt"
else
if [[ ${#PATH_ARGS[@]} -gt 0 ]]; then
CLASSIFIER_OUTPUT=$(bash "$SCRIPT_DIR/classify_subminer_diff.sh" "${PATH_ARGS[@]}")
else
CLASSIFIER_OUTPUT=$(bash "$SCRIPT_DIR/classify_subminer_diff.sh")
fi
printf '%s\n' "$CLASSIFIER_OUTPUT" >"$ARTIFACT_DIR/classification.txt"
while IFS= read -r line; do
case "$line" in
lane:*)
add_lane "${line#lane:}"
;;
esac
done <<<"$CLASSIFIER_OUTPUT"
fi
record_env
printf 'artifact_dir=%s\n' "$ARTIFACT_DIR"
printf 'selected_lanes=%s\n' "$(IFS=,; echo "${SELECTED_LANES[*]}")"
for lane in "${SELECTED_LANES[@]}"; do
case "$lane" in
docs)
run_step "$lane" "docs-test" "bun run docs:test" || break
[[ "$FAILED" == "1" ]] && break
run_step "$lane" "docs-build" "bun run docs:build" || break
;;
config)
run_step "$lane" "test-config" "bun run test:config" || break
;;
core)
run_step "$lane" "typecheck" "bun run typecheck" || break
[[ "$FAILED" == "1" ]] && break
run_step "$lane" "test-fast" "bun run test:fast" || break
;;
launcher-plugin)
run_step "$lane" "launcher-smoke-src" "bun run test:launcher:smoke:src" || break
[[ "$FAILED" == "1" ]] && break
run_step "$lane" "plugin-src" "bun run test:plugin:src" || break
;;
runtime-compat)
run_step "$lane" "build" "bun run build" || break
[[ "$FAILED" == "1" ]] && break
run_step "$lane" "test-runtime-compat" "bun run test:runtime:compat" || break
[[ "$FAILED" == "1" ]] && break
run_step "$lane" "test-smoke-dist" "bun run test:smoke:dist" || break
;;
real-runtime)
if [[ "$PATH_SELECTION_MODE" != "explicit" ]]; then
record_blocked_step \
"$lane" \
"real-runtime-guard" \
"real-runtime lane requires explicit paths; inferred local git changes are non-authoritative"
break
fi
if [[ "$ALLOW_REAL_RUNTIME" != "1" ]]; then
record_blocked_step \
"$lane" \
"real-runtime-guard" \
"real-runtime lane requested but --allow-real-runtime was not supplied"
break
fi
if ! acquire_real_runtime_lease; then
record_blocked_step \
"$lane" \
"real-runtime-lease" \
"real-runtime lease already held; rerun after the active runtime verification finishes"
break
fi
if ! REAL_RUNTIME_HELPER=$(find_real_runtime_helper); then
record_blocked_step \
"$lane" \
"real-runtime-helper" \
"real-runtime helper not implemented yet"
break
fi
printf -v REAL_RUNTIME_COMMAND \
'SESSION_ID=%q HOME=%q XDG_CONFIG_HOME=%q SUBMINER_MPV_LOG=%q bash %q' \
"$SESSION_ID" \
"$SESSION_HOME" \
"$SESSION_XDG_CONFIG_HOME" \
"$SESSION_MPV_LOG" \
"$REAL_RUNTIME_HELPER"
run_step "$lane" "real-runtime-smoke" "$REAL_RUNTIME_COMMAND" || break
;;
*)
record_failed_step "$lane" "lane-validation" "unknown lane: $lane"
break
;;
esac
if [[ "$FAILED" == "1" || "$BLOCKED" == "1" ]]; then
break
fi
done
FINISHED_AT=$(timestamp_iso)
compute_final_status
write_summary_files
printf 'status=%s\n' "$FINAL_STATUS"
printf 'artifact_dir=%s\n' "$ARTIFACT_DIR"
case "$FINAL_STATUS" in
failed)
printf 'result=failed\n'
printf 'failure_command=%s\n' "$FAILURE_COMMAND"
exit 1
;;
blocked)
printf 'result=blocked\n'
exit 2
;;
*)
printf 'result=ok\n'
exit 0
;;
esac

View File

@@ -1,18 +1,146 @@
---
name: 'subminer-scrum-master'
description: 'Compatibility shim. Canonical SubMiner scrum-master workflow now lives in the repo-local subminer-workflow plugin.'
name: "subminer-scrum-master"
description: "Use in the SubMiner repo when a request should be turned into planned work and driven through execution. Assesses whether backlog tracking is warranted, creates or updates tasks when needed, records a plan, dispatches one or more subagents, and requires verification before handoff."
---
# Compatibility Shim
# SubMiner Scrum Master
Canonical source:
Own workflow, not code by default.
- `plugins/subminer-workflow/skills/subminer-scrum-master/SKILL.md`
Use this skill when the user gives a feature request, bug report, issue, refactor, or implementation ask and the agent should manage intake, planning, backlog hygiene, worker dispatch, and verification through completion.
When this shim is invoked:
## Core Rules
1. Read the canonical plugin-owned skill.
2. Follow the plugin-owned skill as the source of truth.
3. Do not duplicate workflow changes here; update the plugin-owned skill instead.
1. Decide first whether backlog tracking is warranted.
2. If backlog is needed, search first. Update existing work when it clearly matches.
3. If backlog is not needed, keep the process light. Do not invent ticket ceremony.
4. Record a plan before dispatching coding work.
5. Use parent + subtasks for multi-part work when backlog is used.
6. Dispatch conservatively. Parallelize only disjoint write scopes.
7. Require verification before handoff, typically via `subminer-change-verification`.
8. Report backlog actions, dispatched workers, verification, blockers, and remaining risks.
This shim exists so existing repo references and prompts keep resolving during the migration to the repo-local plugin workflow.
## Backlog Decision
Skip backlog when the request is:
- question only
- obvious mechanical edit
- tiny isolated change with no real planning
Use backlog when the work:
- needs planning or scope decisions
- spans multiple phases or subsystems
- is likely to need subagent dispatch
- should remain traceable for handoff/resume
If backlog is used:
- search existing tasks first
- create/update a standalone task for one focused deliverable
- create/update a parent task plus subtasks for multi-part work
- record the implementation plan in the task before implementation begins
## Intake Workflow
1. Parse the request.
Classify it as question, mechanical edit, bugfix, feature, refactor, investigation, or follow-up.
2. Decide whether backlog is needed.
3. If backlog is needed:
- search first
- update existing task if clearly relevant
- otherwise create the right structure
- write the implementation plan before dispatch
4. If backlog is skipped:
- write a short working plan in-thread
- proceed without fake ticketing
5. Choose execution mode:
- no subagents for trivial work
- one worker for focused work
- parallel workers only for disjoint scopes
6. Run verification before handoff.
## Dispatch Rules
The scrum master orchestrates. Workers implement.
- Do not become the default implementer unless delegation is unnecessary.
- Do not parallelize overlapping files or tightly coupled runtime work.
- Give every worker explicit ownership of files/modules.
- Tell every worker other agents may be active and they must not revert unrelated edits.
- Require each worker to report:
- changed files
- tests run
- blockers
Use worker agents for implementation and explorer agents only for bounded codebase questions.
## Verification
Every nontrivial code task gets verification.
Preferred flow:
1. use `subminer-change-verification`
2. start with the cheapest sufficient lane
3. escalate only when needed
4. if worker verification is sufficient, accept it or run one final consolidating pass
Never hand off nontrivial work without stating what was verified and what was skipped.
## Pre-Handoff Policy Checks (Required)
Before handoff, always ask and answer both of these questions explicitly:
1. **Docs update required?**
2. **Changelog fragment required?**
Rules:
- Do not assume silence implies "no." Record an explicit yes/no decision for each item.
- If the answer is yes, either complete the update or report the blocker before handoff.
- Include the final answers in the handoff summary even when both answers are "no."
## Failure / Scope Handling
- If a worker hits ambiguity, pause and ask the user.
- If verification fails, either:
- send the worker back with exact failure context, or
- fix it directly if it is tiny and clearly in scope
- If new scope appears, revisit backlog structure before silently expanding work.
## Representative Flows
### Trivial no-ticket work
- decide backlog is unnecessary
- keep a short plan
- implement directly or with one worker if helpful
- run targeted verification
- report outcome concisely
### Single-task implementation
- search/create/update one task
- record plan
- dispatch one worker
- integrate
- verify
- update task and report outcome
### Parent + subtasks execution
- search/create/update parent task
- create subtasks for distinct deliverables/phases
- record sequencing in the plan
- dispatch workers only where scopes are disjoint
- integrate
- run consolidated verification
- update task state and report outcome
## Output Expectations
At the end, report:
- whether backlog was used and what changed
- which workers were dispatched and what they owned
- what verification ran
- explicit answers to:
- docs update required?
- changelog fragment required?
- blockers, skips, and risks

View File

@@ -61,16 +61,6 @@ jobs:
- name: Test suite (source)
run: bun run test:fast
- name: Coverage suite (maintained source lane)
run: bun run test:coverage:src
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-test-src
path: coverage/test-src/lcov.info
if-no-files-found: error
- name: Launcher smoke suite (source)
run: bun run test:launcher:smoke:src

View File

@@ -1,389 +0,0 @@
name: Prerelease
on:
push:
tags:
- 'v*-beta.*'
- 'v*-rc.*'
concurrency:
group: prerelease-${{ github.ref }}
cancel-in-progress: false
jobs:
quality-gate:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.5
- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.bun/install/cache
node_modules
stats/node_modules
vendor/subminer-yomitan/node_modules
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/subminer-yomitan/package-lock.json') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install dependencies
run: |
bun install --frozen-lockfile
cd stats && bun install --frozen-lockfile
- name: Lint stats (formatting)
run: bun run lint:stats
- name: Build (TypeScript check)
run: bun run typecheck
- name: Test suite (source)
run: bun run test:fast
- name: Coverage suite (maintained source lane)
run: bun run test:coverage:src
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-test-src
path: coverage/test-src/lcov.info
if-no-files-found: error
- name: Launcher smoke suite (source)
run: bun run test:launcher:smoke:src
- name: Upload launcher smoke artifacts (on failure)
if: failure()
uses: actions/upload-artifact@v4
with:
name: launcher-smoke
path: .tmp/launcher-smoke/**
if-no-files-found: ignore
- name: Build (bundle)
run: bun run build
- name: Immersion SQLite verification
run: bun run test:immersion:sqlite:dist
- name: Dist smoke suite
run: bun run test:smoke:dist
build-linux:
needs: [quality-gate]
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.5
- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.bun/install/cache
node_modules
stats/node_modules
vendor/texthooker-ui/node_modules
vendor/subminer-yomitan/node_modules
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install dependencies
run: |
bun install --frozen-lockfile
cd stats && bun install --frozen-lockfile
- name: Build texthooker-ui
run: |
cd vendor/texthooker-ui
bun install
bun run build
- name: Build AppImage
run: bun run build:appimage
- name: Build unversioned AppImage
run: |
shopt -s nullglob
appimages=(release/SubMiner-*.AppImage)
if [ "${#appimages[@]}" -eq 0 ]; then
echo "No versioned AppImage found to create unversioned artifact."
ls -la release
exit 1
fi
cp "${appimages[0]}" release/SubMiner.AppImage
- name: Upload AppImage artifact
uses: actions/upload-artifact@v4
with:
name: appimage
path: release/*.AppImage
build-macos:
needs: [quality-gate]
runs-on: macos-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.5
- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.bun/install/cache
node_modules
stats/node_modules
vendor/texthooker-ui/node_modules
vendor/subminer-yomitan/node_modules
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Validate macOS signing/notarization secrets
run: |
missing=0
for name in CSC_LINK CSC_KEY_PASSWORD APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID; do
if [ -z "${!name}" ]; then
echo "Missing required secret: $name"
missing=1
fi
done
if [ "$missing" -ne 0 ]; then
echo "Set all required macOS signing/notarization secrets and rerun."
exit 1
fi
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- name: Install dependencies
run: |
bun install --frozen-lockfile
cd stats && bun install --frozen-lockfile
- name: Build texthooker-ui
run: |
cd vendor/texthooker-ui
bun install
bun run build
- name: Build signed + notarized macOS artifacts
run: bun run build:mac
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- name: Upload macOS artifacts
uses: actions/upload-artifact@v4
with:
name: macos
path: |
release/*.dmg
release/*.zip
build-windows:
needs: [quality-gate]
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: true
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.5
- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.bun/install/cache
node_modules
stats/node_modules
vendor/texthooker-ui/node_modules
vendor/subminer-yomitan/node_modules
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install dependencies
run: |
bun install --frozen-lockfile
cd stats && bun install --frozen-lockfile
- name: Build texthooker-ui
shell: powershell
run: |
Set-Location vendor/texthooker-ui
bun install
bun run build
- name: Build unsigned Windows artifacts
run: bun run build:win:unsigned
- name: Upload Windows artifacts
uses: actions/upload-artifact@v4
with:
name: windows
path: |
release/*.exe
release/*.zip
if-no-files-found: error
release:
needs: [build-linux, build-macos, build-windows]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download AppImage
uses: actions/download-artifact@v4
with:
name: appimage
path: release
- name: Download macOS artifacts
uses: actions/download-artifact@v4
with:
name: macos
path: release
- name: Download Windows artifacts
uses: actions/download-artifact@v4
with:
name: windows
path: release
- name: Setup Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.5
- name: Cache dependencies
uses: actions/cache@v4
with:
path: |
~/.bun/install/cache
node_modules
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
restore-keys: |
${{ runner.os }}-bun-
- name: Install dependencies
run: bun install --frozen-lockfile
- name: Build Bun subminer wrapper
run: make build-launcher
- name: Verify Bun subminer wrapper
run: dist/launcher/subminer --help >/dev/null
- name: Enforce generated launcher workflow
run: bash scripts/verify-generated-launcher.sh
- name: Verify generated config examples
run: bun run verify:config-example
- name: Package optional assets bundle
run: |
tar -czf "release/subminer-assets.tar.gz" \
config.example.jsonc \
plugin/subminer \
plugin/subminer.conf \
assets/themes/subminer.rasi
- name: Generate checksums
run: |
shopt -s nullglob
files=(release/*.AppImage release/*.dmg release/*.exe release/*.zip release/*.tar.gz dist/launcher/subminer)
if [ "${#files[@]}" -eq 0 ]; then
echo "No release artifacts found for checksum generation."
exit 1
fi
sha256sum "${files[@]}" > release/SHA256SUMS.txt
- name: Get version from tag
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
- name: Generate prerelease notes from pending fragments
run: bun run changelog:prerelease-notes --version "${{ steps.version.outputs.VERSION }}"
- name: Publish Prerelease
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
if gh release view "${{ steps.version.outputs.VERSION }}" >/dev/null 2>&1; then
gh release edit "${{ steps.version.outputs.VERSION }}" \
--draft=false \
--prerelease \
--title "${{ steps.version.outputs.VERSION }}" \
--notes-file release/prerelease-notes.md
else
gh release create "${{ steps.version.outputs.VERSION }}" \
--latest=false \
--prerelease \
--title "${{ steps.version.outputs.VERSION }}" \
--notes-file release/prerelease-notes.md
fi
shopt -s nullglob
artifacts=(
release/*.AppImage
release/*.dmg
release/*.exe
release/*.zip
release/*.tar.gz
release/SHA256SUMS.txt
dist/launcher/subminer
)
if [ "${#artifacts[@]}" -eq 0 ]; then
echo "No release artifacts found for upload."
exit 1
fi
for asset in "${artifacts[@]}"; do
gh release upload "${{ steps.version.outputs.VERSION }}" "$asset" --clobber
done

View File

@@ -4,8 +4,6 @@ on:
push:
tags:
- 'v*'
- '!v*-beta.*'
- '!v*-rc.*'
concurrency:
group: release-${{ github.ref }}
@@ -51,16 +49,6 @@ jobs:
- name: Test suite (source)
run: bun run test:fast
- name: Coverage suite (maintained source lane)
run: bun run test:coverage:src
- name: Upload coverage artifact
uses: actions/upload-artifact@v4
with:
name: coverage-test-src
path: coverage/test-src/lcov.info
if-no-files-found: error
- name: Launcher smoke suite (source)
run: bun run test:launcher:smoke:src
@@ -346,14 +334,6 @@ jobs:
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
- name: Build changelog artifacts for release
run: |
if find changes -maxdepth 1 -name '*.md' -not -name README.md -print -quit | grep -q .; then
bun run changelog:build --version "${{ steps.version.outputs.VERSION }}"
else
echo "No pending changelog fragments found."
fi
- name: Verify changelog is ready for tagged release
run: bun run changelog:check --version "${{ steps.version.outputs.VERSION }}"
@@ -411,64 +391,33 @@ jobs:
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
- name: Check AUR publish prerequisites
id: aur_prereqs
- name: Validate AUR SSH secret
env:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
run: |
set -euo pipefail
if [ -z "${AUR_SSH_PRIVATE_KEY}" ]; then
echo "::warning::Missing AUR_SSH_PRIVATE_KEY; skipping automated AUR publish."
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
echo "Missing required secret: AUR_SSH_PRIVATE_KEY"
exit 1
fi
echo "skip=false" >> "$GITHUB_OUTPUT"
- name: Configure SSH for AUR
id: aur_ssh
if: steps.aur_prereqs.outputs.skip != 'true'
env:
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
run: |
set -euo pipefail
if install -dm700 ~/.ssh \
&& printf '%s\n' "${AUR_SSH_PRIVATE_KEY}" > ~/.ssh/aur \
&& chmod 600 ~/.ssh/aur \
&& ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts \
&& chmod 644 ~/.ssh/known_hosts; then
echo "skip=false" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "::warning::Unable to configure SSH for AUR; skipping automated AUR publish."
echo "skip=true" >> "$GITHUB_OUTPUT"
install -dm700 ~/.ssh
printf '%s\n' "${AUR_SSH_PRIVATE_KEY}" > ~/.ssh/aur
chmod 600 ~/.ssh/aur
ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: Clone AUR repo
id: aur_clone
if: steps.aur_prereqs.outputs.skip != 'true' && steps.aur_ssh.outputs.skip != 'true'
env:
GIT_SSH_COMMAND: ssh -i ~/.ssh/aur -o IdentitiesOnly=yes
run: |
set -euo pipefail
attempts=3
for attempt in $(seq 1 "$attempts"); do
if git clone ssh://aur@aur.archlinux.org/subminer-bin.git aur-subminer-bin; then
echo "skip=false" >> "$GITHUB_OUTPUT"
exit 0
fi
rm -rf aur-subminer-bin
if [ "$attempt" -lt "$attempts" ]; then
sleep $((attempt * 15))
fi
done
echo "::warning::Unable to clone subminer-bin from AUR after ${attempts} attempts; skipping automated AUR publish."
echo "skip=true" >> "$GITHUB_OUTPUT"
run: git clone ssh://aur@aur.archlinux.org/subminer-bin.git aur-subminer-bin
- name: Download release assets for AUR
if: steps.aur_prereqs.outputs.skip != 'true' && steps.aur_ssh.outputs.skip != 'true' && steps.aur_clone.outputs.skip != 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
@@ -482,7 +431,6 @@ jobs:
--pattern "subminer-assets.tar.gz"
- name: Update AUR packaging metadata
if: steps.aur_prereqs.outputs.skip != 'true' && steps.aur_ssh.outputs.skip != 'true' && steps.aur_clone.outputs.skip != 'true'
run: |
set -euo pipefail
version_no_v="${{ steps.version.outputs.VERSION }}"
@@ -497,7 +445,6 @@ jobs:
--assets ".tmp/aur-release-assets/subminer-assets.tar.gz"
- name: Commit and push AUR update
if: steps.aur_prereqs.outputs.skip != 'true' && steps.aur_ssh.outputs.skip != 'true' && steps.aur_clone.outputs.skip != 'true'
working-directory: aur-subminer-bin
env:
GIT_SSH_COMMAND: ssh -i ~/.ssh/aur -o IdentitiesOnly=yes
@@ -511,16 +458,4 @@ jobs:
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add PKGBUILD .SRCINFO
git commit -m "Update to ${{ steps.version.outputs.VERSION }}"
attempts=3
for attempt in $(seq 1 "$attempts"); do
if git push origin HEAD:master; then
exit 0
fi
if [ "$attempt" -lt "$attempts" ]; then
sleep $((attempt * 15))
fi
done
echo "::warning::Unable to push the AUR update after ${attempts} attempts; GitHub release is published, but subminer-bin needs manual follow-up."
git push origin HEAD:master

1
.gitignore vendored
View File

@@ -9,7 +9,6 @@ out/
dist/
release/
build/yomitan/
coverage/
# Launcher build artifact (produced by make build-launcher)
/subminer

View File

@@ -83,6 +83,7 @@ This project uses Backlog.md MCP for all task and project management activities.
- **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

View File

@@ -1,266 +0,0 @@
# Backlog
Purpose: lightweight repo-local task board. Seeded with current testing / coverage work.
Status keys:
- `todo`: not started
- `doing`: in progress
- `blocked`: waiting
- `done`: shipped
Priority keys:
- `P0`: urgent / release-risk
- `P1`: high value
- `P2`: useful cleanup
- `P3`: nice-to-have
## Active
| ID | Pri | Status | Area | Title |
| ------ | --- | ------ | -------------- | --------------------------------------------------- |
| SM-013 | P1 | done | review-followup | Address PR #36 CodeRabbit action items |
## Ready
| ID | Pri | Status | Area | Title |
| ------ | --- | ------ | ----------------- | ---------------------------------------------------------------- |
| SM-001 | P1 | todo | launcher | Add tests for CLI parser and args normalizer |
| SM-002 | P1 | todo | immersion-tracker | Backfill tests for uncovered query exports |
| SM-003 | P1 | todo | anki | Add focused field-grouping service + merge edge-case tests |
| SM-004 | P2 | todo | tests | Extract shared test utils for deps factories and polling helpers |
| SM-005 | P2 | todo | tests | Strengthen weak assertions in app-ready and IPC tests |
| SM-006 | P2 | todo | tests | Break up monolithic youtube-flow and subtitle-sidebar tests |
| SM-007 | P2 | todo | anilist | Add tests for AniList rate limiter |
| SM-008 | P3 | todo | subtitles | Add core subtitle-position persistence/path tests |
| SM-009 | P3 | todo | tokenizer | Add tests for JLPT token filter |
| SM-010 | P1 | todo | immersion-tracker | Refactor storage + immersion-tracker service into focused modules |
| SM-011 | P1 | done | tests | Add coverage reporting for maintained test lanes |
| SM-012 | P2 | done | config/runtime | Replace JSON serialize-clone helpers with structured cloning |
## Icebox
None.
## Ticket Details
### SM-001
Title: Add tests for CLI parser and args normalizer
Priority: P1
Status: done
Scope:
- `launcher/config/cli-parser-builder.ts`
- `launcher/config/args-normalizer.ts`
Acceptance:
- root options parsing covered
- subcommand routing covered
- invalid action / invalid log level / invalid backend cases covered
- target classification covered: file, directory, URL, invalid
### SM-002
Title: Backfill tests for uncovered query exports
Priority: P1
Status: todo
Scope:
- `src/core/services/immersion-tracker/query-*.ts`
Targets:
- headword helpers
- anime/media detail helpers not covered by existing wrapper tests
- lexical detail / appearance helpers
- maintenance helpers beyond `deleteSession` and `upsertCoverArt`
Acceptance:
- every exported query helper either directly tested or explicitly justified as covered elsewhere
- at least one focused regression per complex SQL branch / aggregation branch
### SM-003
Title: Add focused field-grouping service + merge edge-case tests
Priority: P1
Status: todo
Scope:
- `src/anki-integration/field-grouping.ts`
- `src/anki-integration/field-grouping-merge.ts`
Acceptance:
- auto/manual/disabled flow branches covered
- duplicate-card preview failure path covered
- merge edge cases covered: empty fields, generated media fallback, strict grouped spans, audio synchronization
### SM-004
Title: Extract shared test utils for deps factories and polling helpers
Priority: P2
Status: todo
Scope:
- common `makeDeps` / `createDeps` helpers
- common `waitForCondition`
Acceptance:
- shared helper module added
- at least 3 duplicated polling helpers removed
- at least 5 duplicated deps factories consolidated or clearly prepared for follow-up migration
### SM-005
Title: Strengthen weak assertions in app-ready and IPC tests
Priority: P2
Status: todo
Scope:
- `src/core/services/app-ready.test.ts`
- `src/core/services/ipc.test.ts`
Acceptance:
- replace broad `assert.ok(...)` presence checks with exact value / order assertions where expected value known
- handler registration tests assert channel-specific behavior, not only existence
### SM-006
Title: Break up monolithic youtube-flow and subtitle-sidebar tests
Priority: P2
Status: todo
Scope:
- `src/main/runtime/youtube-flow.test.ts`
- `src/renderer/modals/subtitle-sidebar.test.ts`
Acceptance:
- reduce single-test breadth
- split largest tests into focused cases by behavior
- keep semantics unchanged
### SM-007
Title: Add tests for AniList rate limiter
Priority: P2
Status: todo
Scope:
- `src/core/services/anilist/rate-limiter.ts`
Acceptance:
- capacity-window wait behavior covered
- `x-ratelimit-remaining` + reset handling covered
- `retry-after` handling covered
### SM-008
Title: Add core subtitle-position persistence/path tests
Priority: P3
Status: todo
Scope:
- `src/core/services/subtitle-position.ts`
Acceptance:
- save/load persistence covered
- fallback behavior covered
- path normalization behavior covered for URL vs local target
### SM-009
Title: Add tests for JLPT token filter
Priority: P3
Status: todo
Scope:
- `src/core/services/jlpt-token-filter.ts`
Acceptance:
- excluded term membership covered
- ignored POS1 membership covered
- exported list / entry consistency covered
### SM-010
Title: Refactor storage + immersion-tracker service into focused layers without API changes
Priority: P1
Status: todo
Scope:
- `src/core/database/storage/storage.ts`
- `src/core/database/storage/schema.ts`
- `src/core/database/storage/cover-blob.ts`
- `src/core/database/storage/records.ts`
- `src/core/database/storage/write-path.ts`
- `src/core/services/immersion-tracker/youtube.ts`
- `src/core/services/immersion-tracker/youtube-manager.ts`
- `src/core/services/immersion-tracker/write-queue.ts`
- `src/core/services/immersion-tracker/immersion-tracker-service.ts`
Acceptance:
- behavior and public API remain unchanged for all callers
- `storage.ts` responsibilities split into DDL/migrations, cover blob helpers, record CRUD, and write-path execution
- `immersion-tracker-service.ts` reduces to session state, media change orchestration, query proxies, and lifecycle
- YouTube code split into pure utilities, a stateful manager (`YouTubeManager`), and a dedicated write queue (`WriteQueue`)
- removed `storage.ts` is replaced with focused modules and updated imports
- no API or migration regressions; existing tests for trackers/storage coverage remain green or receive focused updates
### SM-011
Title: Add coverage reporting for maintained test lanes
Priority: P1
Status: done
Scope:
- `package.json`
- CI workflow files under `.github/`
- `docs/workflow/verification.md`
Acceptance:
- at least one maintained test lane emits machine-readable coverage output
- CI surfaces coverage as an artifact, summary, or check output
- local contributor path for coverage is documented
- chosen coverage path works with Bun/TypeScript lanes already maintained by the repo
Implementation note:
- Added `bun run test:coverage:src` for the maintained source lane via a sharded coverage runner, with merged LCOV output at `coverage/test-src/lcov.info` and CI/release artifact upload as `coverage-test-src`.
### SM-012
Title: Replace JSON serialize-clone helpers with structured cloning
Priority: P2
Status: todo
Scope:
- `src/runtime-options.ts`
- `src/config/definitions.ts`
- `src/config/service.ts`
- `src/main/controller-config-update.ts`
Acceptance:
- runtime/config clone helpers stop using `JSON.parse(JSON.stringify(...))`
- replacement preserves current behavior for plain config/runtime objects
- focused tests cover clone/merge behavior that could regress during the swap
- no new clone helper is introduced in these paths without a documented reason
Done:
- replaced JSON serialize-clone call sites in runtime/config/controller update paths with `structuredClone`
- updated focused tests and fixtures to cover detached clone behavior and guard against regressions
### SM-013
Title: Address PR #36 CodeRabbit action items
Priority: P1
Status: done
Scope:
- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh`
- `scripts/subminer-change-verification.test.ts`
- `src/core/services/immersion-tracker/query-sessions.ts`
- `src/core/services/immersion-tracker/query-trends.ts`
- `src/core/services/immersion-tracker/maintenance.ts`
- `src/main/boot/services.ts`
- `src/main/character-dictionary-runtime/zip.test.ts`
Acceptance:
- fix valid open CodeRabbit findings on PR #36
- add focused regression coverage for behavior changes where practical
- verify touched tests plus typecheck stay green
Done:
- hardened `--artifact-dir` validation in the verification script
- fixed trend aggregation rounding and monthly ratio bucketing
- preserved unwatched anime episodes in episode queries
- restored seconds-based aggregate timestamps in shared maintenance
- fixed the startup refactor compile break by making the predicates local at the call site
- verified with `bun test src/core/services/immersion-tracker/__tests__/query.test.ts src/core/services/immersion-tracker/__tests__/query-split-modules.test.ts` and `bun run typecheck`

View File

@@ -1,232 +1,5 @@
# Changelog
## v0.11.2 (2026-04-07)
### Changed
- Launcher: Replaced the launcher-only fullscreen toggle with `mpv.launchMode` so SubMiner-managed mpv playback can start in normal, maximized, or fullscreen mode.
### Fixed
- Launcher: Fixed launcher-managed mpv spawning to force an explicit X11 GPU path when Wayland trackers are unavailable.
- Launcher: Local playback now promotes a single unlabeled external subtitle sidecar to the primary slot instead of leaving mpv's embedded English auto-selection in place.
- Release: Fixed Linux AppImage startup packaging so Chromium child relaunches can resolve the bundled `libffmpeg.so` instead of crash-looping on startup.
## v0.11.1 (2026-04-04)
### Fixed
- Release: Linux packaged builds now expose the canonical `SubMiner` app identity to Electron's startup metadata so native Wayland compositors stop reporting the window class/app-id as lowercase `subminer`.
- Linux: Linux now restores the runtime options, Jimaku, and Subsync shortcuts after the Electron 39 regression by routing those actions through the overlay's mpv/IPC shortcut path.
## v0.11.0 (2026-04-03)
### Added
- Overlay: Added a playlist browser overlay modal for browsing sibling video files and the live mpv queue during playback.
- Overlay: Added the default `Ctrl+Alt+P` keybinding to open the playlist browser and manage queue order without leaving playback.
### Changed
- Setup: Made mpv plugin installation mandatory in the first-run setup flow, removed the skip path, and kept Finish disabled until the plugin is installed.
- Setup: Clarified that the mpv plugin requirement applies to setup on every platform, while the optional `SubMiner mpv` shortcut remains the recommended Windows playback entry point.
- Launcher: Streamlined Windows setup and config by making the `SubMiner mpv` shortcut self-contained and keeping `mpv.executablePath` as the simple fallback when `mpv.exe` is not on `PATH`.
- Overlay: Changed fresh-install default config to keep texthooker and stats from auto-opening browser tabs.
- Overlay: Changed fresh-install default config to enable AnkiConnect, Discord Rich Presence, subtitle-sidebar, and Yomitan-popup auto-pause by default, while disabling controller input by default.
### Fixed
- Main: Resolve the YouTube playback socket path lazily so startup honors CLI and config overrides.
- Main: Add regression coverage for the lazy socket-path lookup during Windows mpv startup.
- Main: Keep integrated `--start --texthooker` launches on the full app-ready startup path so the texthooker page and websocket servers start together during normal playback startup.
- Main: Stop the mpv/plugin auto-start flow from spawning a separate standalone texthooker helper during normal `subminer <video>` launches.
- Overlay: Keep tracked macOS visible overlays click-through by default so subtitle sidebar passthrough works immediately without requiring a subtitle hover cycle first.
- Overlay: Add regression coverage for the macOS visible-overlay passthrough default.
- Anilist: Stop AniList post-watch from sending a second progress update when the current episode was already satisfied by a ready retry item in the same watch-completion pass.
- Anilist: Add regression coverage for the retry-queue plus live-update duplicate path.
- Overlay: Fixed Kiku duplicate grouping to reuse duplicate note IDs from both generic sentence-card creation and Yomitan popup mining instead of running extra duplicate scans after add.
- Overlay: Fixed the Yomitan popup mining flow to add cards in the background while keeping the stock popup progress feedback, then pause playback and close the lookup popup before the Kiku merge modal opens.
- Overlay: Fixed configured subtitle-jump keybindings so backward and forward subtitle seeks keep playback paused when invoked from a paused state.
- Launcher: Fixed the Windows `SubMiner mpv` shortcut and `SubMiner.exe --launch-mpv` flow to launch mpv with SubMiner's required default args directly instead of requiring an `mpv.conf` profile named `subminer`.
- Launcher: Clarified the Windows install and usage docs so the shortcut path is documented as self-contained, while the optional `subminer` mpv profile remains available for manual mpv launches.
- Launcher: Hardened the first-run setup blocker copy and stale custom-scheme handling so setup messages stay aligned with config, plugin, and dictionary readiness.
- Launcher: Fixed the Windows `SubMiner mpv` shortcut idle launch so loading a video after opening the shortcut keeps mpv in the expected SubMiner-managed session, auto-starts the overlay, and re-arms subtitle auto-selection for the newly opened file.
- Launcher: Removed the redundant `.` subtitle search path from the Windows shortcut launch args and deduped repeated subtitle source tracks in the manual sync picker so duplicate external subtitle entries no longer appear from the shortcut path.
- Playback: Fixed managed local playback so duplicate startup-ready retries no longer unpause media after a later manual pause on the same file.
- Playback: Fixed managed local subtitle auto-selection so local files reuse configured primary and secondary subtitle language priorities instead of staying on mpv's initial `sid=auto` guess.
- Launcher: Added a blank-by-default `mpv.executablePath` override for Windows playback so users can point SubMiner at `mpv.exe` when it is not on `PATH`.
- Launcher: Kept the Windows shortcut and `--launch-mpv` flow simple by preserving PATH auto-discovery as the default and exposing the override in first-run setup.
- Launcher: Added `windows` as a recognized launcher backend option and auto-detection target on Windows.
- Launcher: Honored `SUBMINER_YTDLP_BIN` consistently across YouTube playback URL resolution, track probing, subtitle downloads, and metadata probing.
- Launcher: Kept the first-run setup window from navigating away on unexpected URLs.
- Launcher: Made Windows mpv honor an explicitly configured executable path instead of silently falling back to PATH.
- Launcher: Hardened `--launch-mpv` parsing and Windows binary resolution so valueless flags do not swallow media targets and symlinked launcher installs do not short-circuit PATH lookup.
- Launcher: Fixed first-run setup blocking playback on macOS when the SubMiner mpv plugin was already installed at the canonical `~/.config/mpv` path.
- Launcher: Fixed setup gating so stale cancelled setup state no longer prevents playback when the canonical mpv plugin entrypoint already exists.
- Playback: Prevented stale async playlist-browser subtitle rearm callbacks from overriding newer subtitle selections during rapid file changes.
### Docs
- Docs Site: Added a dedicated Subtitle Sidebar guide and linked it from the homepage and configuration docs.
- Docs Site: Linked Jimaku integration from the homepage to its dedicated docs page.
- Docs Site: Refreshed docs-site theme tokens and hover/selection styling for the updated pages.
### Internal
- Release: Retried AUR clone and push operations in the tagged release workflow.
- Release: Kept GitHub Releases green when AUR publish flakes and needs manual follow-up.
- Release: Updated Electron to 39.8.6 and pinned patched transitive build dependencies to clear the reported high-severity audit findings.
## v0.10.0 (2026-03-29)
### Changed
- Integrations: Replaced the deprecated Discord Rich Presence wrapper with the maintained `@xhayper/discord-rpc` package.
### Fixed
- Stats: Fixed stats startup so the immersion tracker can run when `Bun.serve` is unavailable.
- Stats: Stats server now falls back to a Node `http` listener in Electron/runtime paths that do not expose Bun.
- Overlay: Fixed the macOS visible-overlay toggle path so manual hides stay hidden and the plugin uses the explicit visible-overlay toggle command.
- Subtitle Sidebar: Restored macOS mpv passthrough while the overlay subtitle sidebar is open so clicks outside the sidebar can refocus mpv and keep native keybindings working.
### Internal
- Release: Added a maintained source coverage lane that shards Bun coverage one test file at a time and merges LCOV output into `coverage/test-src/lcov.info`.
- Release: CI and release quality-gate now upload the merged source-lane LCOV artifact for inspection.
- Runtime: Extracted remaining inline runtime logic from `src/main.ts` into dedicated runtime modules and composer helpers.
- Runtime: Added focused regression tests for the extracted runtime/composer boundaries.
- Runtime: Updated task tracking notes to mark TASK-238.6 complete and confirm follow-on boot-phase split can be deferred.
- Runtime: Split `src/main.ts` boot wiring into dedicated `src/main/boot/services.ts`, `src/main/boot/runtimes.ts`, and `src/main/boot/handlers.ts` modules.
- Runtime: Added focused tests for the new boot-phase seams and kept the startup/typecheck/build verification lanes green.
- Runtime: Updated internal architecture/task docs to record the boot-phase split and new ownership boundary.
## v0.9.3 (2026-03-25)
### Changed
- Launcher: Moved YouTube primary subtitle language defaults to `youtube.primarySubLanguages`.
- Launcher: Removed the placeholder YouTube subtitle retime step and now uses downloaded primary subtitle tracks directly, so there is no fake path rewrite before playback/sidebar loading.
- YouTube: Removed the `src/core/services/youtube/retime` helper and its tests after retiring the internal retime strategy.
- Docs: Clarified optional `alass` / `ffsubsync` subtitle-sync requirements and setup steps, including fallback behavior when sync tools are absent.
- Launcher: Removed the old `youtubeSubgen.primarySubLanguages` config path from the generated config and docs.
## v0.9.2 (2026-03-25)
### Fixed
- Overlay: Fixed overlay pointer tracking so Windows click-through toggles immediately when the cursor enters or leaves subtitle regions, without waiting for a later hover resync.
- Overlay: Fixed Windows overlay window tracking on scaled displays by converting native tracked window bounds to Electron DIP coordinates before applying overlay bounds.
- Launcher: Fixed Windows direct `--youtube-play` startup so MPV boots reliably, stays paused until the app-owned subtitle flow is ready, and reuses an already-running SubMiner instance when available.
- Launcher: Fixed standalone Windows `--youtube-play` sessions so closing MPV fully exits SubMiner instead of leaving hidden overlay windows or a background process behind.
- Overlay: Fixed `subminer <youtube-url>` on Linux so the YouTube playback flow waits for Yomitan to load before creating the overlay window, avoiding the broken lookup popup state that previously required a manual overlay refresh.
## v0.9.1 (2026-03-24)
### Changed
- Release: Reduced packaged release size by excluding duplicate `extraResources` payload and pruning docs, tests, sourcemaps, and other source-only files from Electron bundles.
### Fixed
- Overlay: Restored controller navigation and lookup/mining controls while the subtitle sidebar is open, while keeping true modal dialogs blocking controller actions.
- Tokenizer: Fixed subtitle annotation clearing so explanatory contrast endings like `んですけど` are excluded consistently across the shared tokenizer filter and annotation stage.
## v0.9.0 (2026-03-23)
### Added
- Docs: Added a new WebSocket / Texthooker API and integration guide covering WebSocket payloads, custom client patterns, mpv plugin automation, and webhook-style relay examples. Linked from configuration and mining workflow docs for easier discovery.
### Changed
- Launcher: Added an app-owned YouTube subtitle flow that pauses mpv, uses absPlayer-style YouTube timedtext parsing/conversion to download subtitle tracks, and injects them as external files before playback resumes.
- Launcher: Changed YouTube subtitle startup to auto-load the best-available primary and secondary subtitle tracks at launch instead of forcing the picker modal first. Secondary subtitle failures no longer block playback resume.
- Launcher: Added `Ctrl+Alt+C` as the default keybinding to manually open the YouTube subtitle picker during active YouTube playback.
- Launcher: Added yt-dlp metadata probing so YouTube playback and immersion tracking record canonical video title and channel metadata.
- Launcher: Stopped forcing `--ytdl-raw-options=` before user-provided mpv options so existing YouTube cookie integrations in user `--args` are no longer clobbered.
- Launcher: Disabled mpv native YouTube subtitle auto-loading for the app-owned flow so injected external subtitle files remain authoritative.
- Launcher: Added OSD status messages for YouTube playback startup, subtitle acquisition, and subtitle loading so the flow stays visible before and during the picker.
- Subtitle Sidebar: Added startup-auto-open controls and resume positioning improvements so the sidebar jumps directly to the first resolved active cue.
- Subtitle Sidebar: Improved subtitle prefetch and embedded overlay passthrough sync so sidebar and overlay subtitle states stay consistent across media transitions.
- Subtitle Sidebar: Updated scroll handling, embedded layout styling, and active-cue visual behavior.
- Stats: Stats Library tab now displays YouTube video title, channel name, and channel thumbnail for YouTube media entries, with retry logic to fill in metadata that arrives after initial load.
### Fixed
- Launcher: Fixed Anki media mining for mpv YouTube streams by unwrapping the stream URL so audio and screenshot capture work correctly for YouTube playback sessions.
- Immersion: Fixed YouTube media path handling in the immersion runtime and tracking so YouTube sessions record correct media references, AniList guessing skips YouTube URLs, and post-watch state transitions do not fire for YouTube media.
- Launcher: Fixed startup-launched YouTube playback so primary subtitle overlay updates continue after auto-load completes.
- Launcher: Fixed auto-loaded YouTube primary subtitles so parsed cues appear in the subtitle sidebar without needing a manual picker retry.
- Launcher: Fixed the YouTube picker to guard against duplicate subtitle submissions and tightened YouTube URL detection so follow-up runtime flows only treat real YouTube hosts as YouTube playback.
- Launcher: Fixed primary subtitle failure notifications being shown while app-owned YouTube subtitle probing and downloads are still in flight.
- Launcher: Preserved existing authoritative YouTube subtitle tracks when available; downloaded tracks are used only to fill missing sides, and native mpv secondary subtitle rendering is hidden so the overlay remains the sole secondary display.
## v0.8.0 (2026-03-22)
### Added
- Overlay: Added the subtitle sidebar feature with a new `subtitleSidebar` configuration surface and rendered sidebar modal with cue list rendering, click-to-seek, active-cue highlighting, and embedded layout support.
- IPC: Added sidebar snapshot plumbing between renderer and main process for overlay/sidebar synchronization.
### Changed
- Config: Added hot-reloadable sidebar options for enablement, layout, visibility, typography, opacity, sizing, and interaction behavior (`autoOpen`, `pauseOnHover`, `autoScroll`, toggle key).
- Docs: Added full `subtitleSidebar` documentation coverage, including sample config, option table, and toggle shortcut notes.
- Runtime: Improved subtitle prefetch/rendering flow so sidebar and overlay subtitle states stay in sync across media transitions.
### Fixed
- Overlay: Kept sidebar cue tracking stable across playback transitions and timing edge cases.
- Overlay: Improved sidebar resume/start behavior to jump directly to the first resolved active cue.
- Overlay: Stopped stale subtitle refreshes from regressing active-cue and text state.
## v0.7.0 (2026-03-19)
### Added
- Immersion: Added Mine Word, Mine Sentence, and Mine Audio buttons to word detail example lines in the stats dashboard.
- Immersion: Mine Word creates a full Yomitan card (definition, reading, pitch accent) via the hidden search page bridge, then enriches with sentence audio, screenshot, and metadata extracted from the source video.
- Immersion: Mine Sentence and Mine Audio create cards directly with appropriate Lapis/Kiku flags, sentence highlighting, and media from the source file.
- Immersion: Media generation (audio + image/AVIF) runs in parallel and respects all AnkiConnect config options.
- Immersion: Added word exclusion list to the Vocabulary tab with localStorage persistence and a management modal.
- Immersion: Fixed truncated readings in the frequency rank table (e.g. お前 now shows おまえ instead of まえ).
- Immersion: Clicking a bar in the Top Repeated Words chart now opens the word detail panel.
- Immersion: Secondary subtitle text is now stored alongside primary subtitle lines for use as translation when mining cards from the stats page.
- Stats: Added `subminer stats -b` to start or reuse a dedicated background stats server without blocking normal SubMiner instances.
- Stats: Added `subminer stats -s` to stop the dedicated background stats server without closing browser tabs.
- Stats: Stats server startup now reuses a running background stats daemon instead of trying to bind a second local server in another SubMiner instance.
- Launcher: Added launcher passthrough for `-a/--args` so mpv receives raw extra launch flags (`--fs`, `--ytdl-format`, custom audio/video settings, etc.) from the `subminer` command.
- Launcher: Added `subminer stats` to launch the local stats dashboard, force-start the stats server on demand, and open the dashboard in your browser.
- Launcher: Added `subminer stats cleanup` to backfill vocabulary metadata and prune stale or excluded immersion rows on demand.
- Launcher: Added `stats.autoOpenBrowser` so browser launch after `subminer stats` can be enabled or disabled explicitly.
- Immersion: Added a local stats dashboard for immersion tracking with Overview, Anime, Trends, Vocabulary, and Sessions views.
- Immersion: Added anime progress, episode completion, Anki card links, and occurrence drill-down across the stats dashboard.
- Immersion: Added richer session timelines with new-word activity, cumulative totals, and pause/seek/card event markers.
- Immersion: Added completed-episodes and completed-anime totals to the Overview tracking snapshot.
### Changed
- Anki: Changed known-word cache settings to live under `ankiConnect.knownWords` instead of mixing them into `ankiConnect.nPlusOne`.
- Anki: Kept legacy `ankiConnect.nPlusOne` known-word keys and older `ankiConnect.behavior.nPlusOne*` keys as deprecated compatibility fallbacks.
- Stats: Added session deletion to the Sessions tab with the same confirmation prompt used by anime episode/session deletes, and removed all associated session rows from the stats database.
- Immersion: Kept immersion tracking history by default while preserving daily/monthly rollup maintenance.
- Immersion: Added exact lifetime summary reads for overview/anime/media stats so dashboard totals no longer depend on rescanning raw telemetry.
- Immersion: Reduced tracker storage overhead by removing duplicated subtitle text from subtitle-line event payloads.
- Immersion: Deduplicated episode cover-art blobs through a shared blob store and updated cover-art reads/writes to resolve shared images correctly.
- Immersion: Added indexes for large-history session, telemetry, vocabulary, kanji, and cover-art queries to keep dashboard reads fast as the SQLite database grows.
- Immersion: Renamed the stats dashboard's Anime tab to Library so the media browser label matches non-anime sources like YouTube and other yt-dlp-backed content.
- Anilist: Standardized episode completion threshold by introducing `DEFAULT_MIN_WATCH_RATIO` and using it for both local watched state transitions and AniList post-watch progress updates.
- Anilist: Episode auto-marking now uses the same threshold as AniList (`85%`), removing divergent completion behavior.
- Overlay: Excluded interjections and sound-effect tokens from subtitle annotation styling so they no longer inherit misleading lexical highlight treatment while still remaining visible and hoverable as plain subtitle tokens.
- Overlay: Expanded subtitle annotation noise filtering to also strip annotation metadata from standalone grammar-only helper tokens such as particles, auxiliaries, adnominals, common explanatory endings like `んです` / `のだ`, and merged trailing quote-particle forms like `...って` while keeping them tokenized for hover lookup.
### Fixed
- Launcher: Fixed mpv Lua plugin binary auto-detection on Linux to also search `/usr/bin/subminer` and `/usr/local/bin/subminer` (lowercase), matching the conventional Unix wrapper name used by packaged installs such as the AUR package.
- Stats: Fixed the in-app stats overlay so it connects to the configured `stats.serverPort` instead of falling back to the default port.
- Overlay: Fixed subtitle frequency tagging for merged lookup-backed tokens like `陰に` by falling back to exact surface-form Yomitan frequencies when the normalized headword lookup misses.
- Overlay: Fixed MeCab merged-token position mapping across line breaks so merged content-plus-particle tokens like `陰に` keep their matched Yomitan frequency instead of inheriting shifted POS tags.
- Overlay: Fixed grouped frequency parsing in both Yomitan and fallback frequency-dictionary lookups so display values like `118,121` use the leading rank instead of collapsing the rank and occurrence count into `118121`.
- Overlay: Fixed frequency-rank ingestion to ignore Yomitan dictionaries explicitly marked `occurrence-based`, so raw occurrence counts are no longer treated as subtitle rank values.
- Overlay: Fixed inflected headword frequency tagging to prefer ranks from the selected Yomitan `termsFind` popup entry itself, ordered by configured dictionary priority, so forms like `潜み` use primary-dictionary ranks like `4073` before falling back to lower-priority raw lemma metadata such as `CC100`.
- Overlay: Fixed annotation-stage frequency filtering so exact kanji noun tokens like `者` keep their matched rank even when MeCab labels them `名詞/非自立`, instead of dropping the highlight after scan-time frequency lookup succeeds.
- Anki: Fixed repeated character-dictionary startup work by scheduling auto-sync only from mpv media-path changes instead of also re-triggering it from connection and media-title events for the same title.
- Overlay: Fixed macOS fullscreen overlay stability by keeping the passive visible overlay from stealing focus, re-raising the overlay window when reasserting its macOS topmost level, and tolerating one transient macOS tracker/helper miss before hiding the overlay.
- Overlay: Kept subtitle tokenization warmup one-shot for the lifetime of the app so later fullscreen/media churn on macOS does not replay the startup warmup gate after the first file is ready.
- Overlay: Added a bounded macOS tracker loss-grace window so fullscreen enter/leave transitions do not immediately hide and reload the overlay when the helper briefly loses the mpv window.
- Overlay: Skipped subtitle/tokenization refresh invalidation on character-dictionary auto-sync completion when the dictionary was already current, preventing startup flash/reload loops on unchanged media.
- Stats: Fixed session stats so known-word counts track real known-word occurrences without collapsing subtitle-line gaps.
- Stats: Fixed session word totals in session-facing stats views to prefer token counts when available, preventing known words from exceeding total words in the session chart.
- Stats: Fixed the stats Vocabulary tab blank-screen regression caused by a hook-order crash after vocabulary data finished loading.
- Anki: Fixed card-mine OSD feedback so the final mine result stops the Anki spinner first, then shows a single-line `✓`/`x` status without being overwritten by a later spinner tick.
- Stats: Removed the misleading `New words` series from expanded session charts; session detail now shows only the real total-word and known-word lines.
- Stats: Restored the cross-anime word table behavior in stats vocabulary surfaces so shared vocabulary entries no longer disappear or merge incorrectly across related media.
- Stats: `subminer stats -b` now runs as a standalone background stats daemon instead of reusing the main SubMiner app process, so the overlay app can still be launched separately for normal video watching.
- Stats: Dashboard word mining still works against the background daemon by using a short-lived hidden helper for the Yomitan add-note flow.
- Stats: Load full session timelines by default in stats session detail views so long sessions preserve complete telemetry history instead of being truncated by a fixed sample limit.
- Stats: Replaced heuristic stats word counts with Yomitan token counts, so session, media, anime, and trend subtitle totals now come directly from parsed subtitle tokens.
- Stats: Updated stats UI labels and lookup-rate copy to refer to tokens instead of words where those counts are shown.
- Overlay: Reduced repeated `Overlay loading...` popups on macOS when fullscreen tracker flaps briefly hide and recover the visible overlay.
- Stats: Scaled expanded session-detail known-word charts to the session's actual percentage range so small changes no longer render as a nearly flat line.
- Jlpt: Reduced JLPT dictionary startup log noise by summarizing duplicate surface-form collisions instead of logging one line per duplicate entry.
## v0.6.5 (2026-03-15)
### Internal

280
README.md
View File

@@ -1,182 +1,60 @@
<div align="center">
<img src="assets/SubMiner.png" width="140" alt="SubMiner logo">
# SubMiner
**Sentence-mine from mpv — look up words, one-key Anki export, immersion tracking.**
[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Linux](https://img.shields.io/badge/platform-Linux%20%7C%20macOS%20%7C%20Windows-informational)]()
[![Docs](https://img.shields.io/badge/docs-docs.subminer.moe-blueviolet)](https://docs.subminer.moe)
[![AUR](https://img.shields.io/aur/version/subminer-bin)](https://aur.archlinux.org/packages/subminer-bin)
</div>
---
SubMiner is an Electron overlay for [mpv](https://mpv.io) that turns video into a sentence-mining workstation. Look up any word with [Yomitan](https://github.com/yomidevs/yomitan), mine it to Anki with one key, and track your immersion over time.
<div align="center">
<img src="assets/SubMiner.png" width="160" alt="SubMiner logo">
# SubMiner
Look up words with Yomitan, export to Anki in one key, track your immersion — all without leaving mpv.
[Installation](#quick-start) · [Requirements](#requirements) · [Usage](https://docs.subminer.moe/usage) · [Documentation](https://docs.subminer.moe)
[![Downloads](https://img.shields.io/github/downloads/ksyasuda/SubMiner/total?style=flat-square&color=1a1a2e)](https://github.com/ksyasuda/SubMiner/releases)
[![Release](https://img.shields.io/github/v/release/ksyasuda/SubMiner?style=flat-square&color=1a1a2e)](https://github.com/ksyasuda/SubMiner/releases/latest)
[![AUR](https://img.shields.io/aur/version/subminer-bin?style=flat-square&color=1a1a2e)](https://aur.archlinux.org/packages/subminer-bin)
[![Platform](https://img.shields.io/badge/platform-Linux%20·%20macOS%20·%20Windows-1a1a2e?style=flat-square)](https://github.com/ksyasuda/SubMiner)
[![License](https://img.shields.io/github/license/ksyasuda/SubMiner?style=flat-square&color=1a1a2e)](https://www.gnu.org/licenses/gpl-3.0)
[![TypeScript](https://img.shields.io/badge/TypeScript-1a1a2e?style=flat-square&logo=typescript&logoColor=3178c6)](https://www.typescriptlang.org)
[![SubMiner demo](./assets/minecard.webp)](https://github.com/user-attachments/assets/89e61895-e2b7-4b47-8d50-a35afe4132b2)
[![SubMiner demo (Animated preview)](./assets/minecard.webp)](./assets/minecard.mp4)
</div>
## Features
### Dictionary Lookups
**Dictionary lookups** — Yomitan runs inside the overlay. Hover or navigate to any word for full dictionary popups without leaving mpv.
Yomitan runs inside the overlay. Trigger a lookup on any word for full dictionary popups — definitions, pitch accent, frequency data — without ever leaving mpv.
**One-key Anki mining** — Press one key to create a card with the sentence, audio clip, screenshot, and machine translation from the exact playback moment.
<div align="center">
<img src="docs-site/public/screenshots/yomitan-lookup.png" width="800" alt="Yomitan dictionary popup over annotated subtitles in mpv">
<img src="docs-site/public/screenshots/yomitan-lookup.png" width="800" alt="Yomitan popup with dictionary entry and mine button over annotated subtitles in mpv">
</div>
<br>
### Instant Anki Mining
Create an Anki card with the sentence, audio clip, screenshot, and machine translation from the exact playback moment with one key press, click, or controller input.
**Reading annotations** — Real-time subtitle annotations with N+1 targeting, frequency highlighting, JLPT tags, and a character name dictionary. Grammar-only tokens render as plain text.
<div align="center">
<img src="docs-site/public/screenshots/one-key-mining.png" width="800" alt="Anki card created from SubMiner with sentence, audio, and screenshot">
<img src="docs-site/public/screenshots/annotations.png" width="800" alt="Annotated subtitles with frequency highlighting, JLPT underlines, known words, and N+1 targets">
</div>
<br>
### Reading Annotations
Real-time subtitle annotations with frequency highlighting, JLPT tags, N+1 targeting, and a character name dictionary. Known words fade back; new words stand out. Grammar-only tokens render as plain text so you focus on what matters.
**Immersion dashboard** — Local stats dashboard with watch time, anime progress, vocabulary growth, mining throughput, and session history.
<div align="center">
<img src="docs-site/public/screenshots/annotations.png" width="800" alt="Annotated subtitles with frequency coloring, JLPT underlines, and N+1 targets">
<img src="docs-site/public/screenshots/stats-overview.png" width="800" alt="Stats dashboard with watch time, cards mined, streaks, and tracking snapshot">
</div>
<br>
### Immersion Dashboard
Local stats dashboard — watch time, anime library, vocabulary growth, mining throughput, session history, and trends. All stored locally, no third-party tracking.
**Integrations** — AniList episode tracking, Jellyfin remote playback, Jimaku subtitle downloads, alass/ffsubsync, and an annotated websocket feed for external clients.
<div align="center">
<img src="docs-site/public/screenshots/stats-overview.png" width="800" alt="Stats dashboard showing watch time, cards mined, streaks, and tracking data">
<img src="docs-site/public/screenshots/texthooker.png" width="800" alt="Texthooker page with annotated subtitle lines and frequency highlighting">
</div>
<br>
### Playlist Browser
Browse sibling episode files and the active mpv queue in one overlay modal. Open it with `Ctrl+Alt+P` to append episodes from the current directory, jump to queued items, remove entries, or reorder the playlist without leaving playback.
<div align="center">
<img src="docs-site/public/screenshots/playlist-browser.png" width="800" alt="Stats dashboard showing watch time, cards mined, streaks, and tracking data">
</div>
<br>
### Integrations
<table>
<tr>
<td><b>YouTube</b></td>
<td>Auto-loaded yt-dlp subtitle tracks at startup with config-driven primary/secondary language priorities and a manual overlay picker on demand (<code>Ctrl+Alt+C</code>)</td>
</tr>
<tr>
<td><b>AniList</b></td>
<td>Automatic episode tracking and progress sync</td>
</tr>
<tr>
<td><b>Jellyfin</b></td>
<td>Browse and launch media from your Jellyfin server</td>
</tr>
<tr>
<td><b>Jimaku</b></td>
<td>Search and download Japanese subtitles</td>
</tr>
<tr>
<td><b>alass / ffsubsync</b></td>
<td>Automatic subtitle retiming — requires <code>alass</code> or <code>ffsubsync</code> on your <code>PATH</code> (optional; subtitle syncing is disabled without them)</td>
</tr>
<tr>
<td><b>WebSocket</b></td>
<td>Annotated subtitle feed for external clients (texthooker pages, custom tools)</td>
</tr>
</table>
<div align="center">
<img src="docs-site/public/screenshots/texthooker.png" width="800" alt="Texthooker page receiving annotated subtitle lines via WebSocket">
</div>
<br>
---
## Requirements
| | Required | Recommended | Optional |
| -------------- | --------------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| **Player** | [`mpv`](https://mpv.io) with IPC socket | — | — |
| **Processing** | — | `ffmpeg` (audio clips & screenshots) | `mecab` + `mecab-ipadic` (annotation POS filtering), `guessit` (AniSkip), `alass` / `ffsubsync` (subtitle sync) |
| **Media** | — | — | `yt-dlp`, `chafa`, `ffmpegthumbnailer` |
| **Selection** | — | — | `fzf` / `rofi` |
> [!TIP]
> `ffmpeg` is not strictly required to run SubMiner, but without it audio clips and screenshots will not be attached to Anki cards. Most users will want it installed.
> [!NOTE]
> [`bun`](https://bun.sh) is required if building from source or using the CLI wrapper: `subminer`. Pre-built releases (AppImage, DMG, installer) do not require it.
**Platform-specific:**
| Linux | macOS | Windows |
| ------------------------------------------------------------ | ------------------------ | ------------- |
| Hyprland (`hyprctl`) · X11/Xwayland (`xdotool` + `xwininfo`) | Accessibility permission | No extra deps |
> [!NOTE]
> **Wayland support is compositor-specific.** Wayland has no universal API for window positioning and each compositor exposes its own IPC, so SubMiner needs a dedicated backend per compositor. Hyprland is the only native Wayland backend supported currenlty. All other Linux compositors require both mpv and SubMiner to run under X11 or Xwayland. The launcher detects your compositor and configures this automatically.
<details>
<summary><b>Arch Linux</b></summary>
```bash
paru -S --needed mpv ffmpeg
# Optional
paru -S --needed mecab-git mecab-ipadic yt-dlp fzf rofi chafa ffmpegthumbnailer xdotool xorg-xwininfo
# Optional: subtitle sync (install at least one for subtitle syncing to work)
paru -S --needed alass python-ffsubsync
# X11 / Xwayland (required for non-Hyprland compositors)
paru -S --needed xdotool xorg-xwininfo
```
</details>
<details>
<summary><b>macOS</b></summary>
```bash
brew install mpv ffmpeg
# Optional
brew install mecab mecab-ipadic yt-dlp fzf rofi chafa ffmpegthumbnailer
# Optional: subtitle sync (install at least one for subtitle syncing to work)
brew install alass
pip install ffsubsync
```
Grant Accessibility permission to SubMiner in **System Settings > Privacy & Security > Accessibility**.
</details>
<details>
<summary><b>Windows</b></summary>
Install [`mpv`](https://mpv.io/installation/) and [`ffmpeg`](https://ffmpeg.org/download.html) and ensure both are on your `PATH`.
Optionally install [MeCab for Windows](https://taku910.github.io/mecab/#download) with the UTF-8 dictionary for additional metadata enrichment.
</details>
---
## Quick Start
### 1. Install
### Install
<details>
<summary><b>Arch Linux (AUR)</b></summary>
@@ -197,7 +75,6 @@ git clone https://aur.archlinux.org/subminer-bin.git && cd subminer-bin && makep
<summary><b>Linux (AppImage)</b></summary>
```bash
mkdir -p ~/.local/bin
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/SubMiner.AppImage -O ~/.local/bin/SubMiner.AppImage \
&& chmod +x ~/.local/bin/SubMiner.AppImage
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer -O ~/.local/bin/subminer \
@@ -210,93 +87,50 @@ wget https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer -O ~
</details>
<details>
<summary><b>macOS</b></summary>
<summary><b>macOS / Windows / From source</b></summary>
Download the latest DMG or ZIP from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest) and drag `SubMiner.app` into `/Applications`.
**macOS**Download the latest DMG/ZIP from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest) and drag `SubMiner.app` into `/Applications`.
Also download the `subminer` launcher (recommended):
**Windows** — Download the latest installer or portable `.zip` from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest). Keep `mpv` on `PATH`.
```bash
sudo curl -fSL https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer -o /usr/local/bin/subminer \
&& sudo chmod +x /usr/local/bin/subminer
```
> [!NOTE]
> The `subminer` launcher uses a [Bun](https://bun.sh) shebang. Make sure `bun` is on your `PATH`.
**From source** — See [docs.subminer.moe/installation#from-source](https://docs.subminer.moe/installation#from-source).
</details>
<details>
<summary><b>Windows</b></summary>
### First Launch
Download the latest installer or portable `.zip` from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest). Make sure `mpv` is on your `PATH`.
Run `SubMiner.AppImage` (Linux), `SubMiner.app` (macOS), or `SubMiner.exe` (Windows). On first launch, SubMiner starts in the tray, creates a default config, and opens a setup popup where you can install the mpv plugin and configure Yomitan dictionaries.
**Windows support is experimental.** Core features such as mining, annotations, and dictionary lookups work, but some functionality may be missing or unstable. Bug reports welcome.
**Note:** On Windows the `subminer` launcher requires [`bun`](https://bun.sh) and must be invoked with `bun run subminer` instead of running the script directly. The recommended alternative is the **SubMiner mpv** shortcut created during first-run setup — double-click it, drag files onto it, or run `SubMiner.exe --launch-mpv` from a terminal. See the [Windows mpv Shortcut](https://docs.subminer.moe/usage#windows-mpv-shortcut) section for details.
</details>
<details>
<summary><b>From source</b></summary>
See the [build-from-source guide](https://docs.subminer.moe/installation#from-source).
</details>
### 2. First Launch
### Mine
```bash
subminer app --setup # launch the first-run setup wizard
subminer video.mkv # auto-starts overlay + resumes playback
subminer --start video.mkv # explicit overlay start (if plugin auto_start=no)
subminer stats # open the immersion dashboard
```
SubMiner creates a default config, starts in the system tray, and opens a setup popup that walks you through installing the mpv plugin and configuring Yomitan dictionaries. Follow the on-screen steps to complete setup.
> [!NOTE]
> On Windows, run `SubMiner.exe` directly — it opens the setup wizard automatically on first launch.
### 3. Verify Setup
```bash
subminer doctor # verify mpv, ffmpeg, config, and socket
```
> [!NOTE]
> On Windows, use `bun run subminer doctor` or run `SubMiner.exe` directly — first-run setup will guide you through dependency checks.
### 4. Mine
```bash
subminer video.mkv # play video with overlay
subminer --start video.mkv # explicit overlay start
subminer stats # open immersion dashboard
subminer stats -b # stats daemon in background
subminer stats -s # stop background stats daemon
```
On **Windows**, the `subminer` script must be run with `bun run subminer` (e.g. `bun run subminer video.mkv`). The recommended alternative is the **SubMiner mpv** shortcut (created during setup) or `SubMiner.exe --launch-mpv`. Drag a video file onto the shortcut to play it, or double-click it to open mpv with SubMiner's defaults.
## Documentation
Full guides on configuration, Anki setup, Jellyfin, immersion tracking, and more: **[docs.subminer.moe](https://docs.subminer.moe)**
---
## Requirements
| Required | Optional |
|---|---|
| [`mpv`](https://mpv.io) with IPC socket | `yt-dlp` |
| `ffmpeg` | `guessit` (AniSkip detection) |
| `mecab` + `mecab-ipadic` | `fzf` / `rofi` |
| [`bun`](https://bun.sh) (source builds, Linux wrapper) | `chafa`, `ffmpegthumbnailer` |
| Linux: `hyprctl` or `xdotool` + `xwininfo` | |
| macOS: Accessibility permission | |
Windows uses native window tracking and does not need the Linux compositor tools.
## Documentation
Full guides on configuration, Anki, Jellyfin, immersion tracking, and more at **[docs.subminer.moe](https://docs.subminer.moe)**.
## Acknowledgments
SubMiner builds on the work of these open-source projects:
| Project | Role |
| ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
| [Anacreon-Script](https://github.com/friedrich-de/Anacreon-Script) | Inspiration for the mining workflow |
| [asbplayer](https://github.com/killergerbah/asbplayer) | Inspiration for subtitle sidebar and logic for YouTube subtitle parsing |
| [Bee's Character Dictionary](https://github.com/bee-san/Japanese_Character_Name_Dictionary) | Character name recognition in subtitles |
| [GameSentenceMiner](https://github.com/bpwhelan/GameSentenceMiner) | Inspiration for Electron overlay with Yomitan integration |
| [jellyfin-mpv-shim](https://github.com/jellyfin/jellyfin-mpv-shim) | Jellyfin integration |
| [Jimaku.cc](https://jimaku.cc) | Japanese subtitle search and downloads |
| [Renji's Texthooker Page](https://github.com/Renji-XD/texthooker-ui) | Base for the WebSocket texthooker integration |
| [Yomitan](https://github.com/yomidevs/yomitan) | Dictionary engine powering all lookups and the morphological parser |
| [yomitan-jlpt-vocab](https://github.com/stephenmk/yomitan-jlpt-vocab) | JLPT level tags for vocabulary |
Built on [GameSentenceMiner](https://github.com/bpwhelan/GameSentenceMiner), [Renji's Texthooker Page](https://github.com/Renji-XD/texthooker-ui), [Anacreon-Script](https://github.com/friedrich-de/Anacreon-Script), and [Bee's Character Dictionary](https://github.com/bee-san/Japanese_Character_Name_Dictionary). Subtitles from [Jimaku.cc](https://jimaku.cc). Lookups via [Yomitan](https://github.com/yomidevs/yomitan). JLPT tags from [yomitan-jlpt-vocab](https://github.com/stephenmk/yomitan-jlpt-vocab).
## License

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -1,23 +0,0 @@
---
id: TASK-279
title: Prepare v0.11.2 release notes and verify release gate
status: In Progress
assignee: []
created_date: '2026-04-04 07:38'
labels: []
dependencies: []
priority: high
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Generate release metadata for the pending changelog fragments, review the resulting changelog/release notes output, and run the documented local release gate so the repo is ready for a v0.11.2 cut if checks pass.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 CHANGELOG.md and release/release-notes.md are generated for v0.11.2 using the pending changes fragments.
- [ ] #2 The documented local release gate for release prep is run and the pass/fail state is recorded with any blockers.
- [ ] #3 Any release-prep file updates needed for the generated notes are left in the worktree for review without tagging or pushing.
<!-- AC:END -->

View File

@@ -1,80 +0,0 @@
---
id: TASK-169
title: Cut minor release v0.7.0 for stats and runtime polish
status: Done
assignee:
- codex
created_date: '2026-03-19 17:20'
updated_date: '2026-03-19 17:31'
labels:
- release
- docs
- minor
dependencies:
- TASK-168
references:
- package.json
- README.md
- docs/RELEASING.md
- docs-site/changelog.md
- CHANGELOG.md
- release/release-notes.md
priority: high
ordinal: 108000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Prepare the next release cut as `v0.7.0`, keeping 0-ver semantics by rolling the accumulated stats/dashboard, launcher, overlay, and stability work into the next minor line instead of a `1.0.0` release.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Repository version metadata is updated to `0.7.0`.
- [x] #2 Root release-facing docs are refreshed for the `0.7.0` release cut.
- [x] #3 `CHANGELOG.md` and `release/release-notes.md` contain the committed `v0.7.0` section and consumed fragments are removed.
- [x] #4 Public changelog/docs surfaces reflect the new release.
- [x] #5 Release-prep verification is recorded.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Bump `package.json` to `0.7.0`.
2. Refresh release-facing docs: root `README.md`, release guide versioning note, and public docs changelog summary.
3. Run `bun run changelog:build --version 0.7.0` to commit release artifacts and consume pending fragments.
4. Run release-prep verification (`changelog`, typecheck, tests, docs build if docs-site changed).
5. Update this task with notes, verification, and final summary.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Bumped `package.json` from `0.6.5` to `0.7.0` and refreshed the root release-facing copy in `README.md` so the release prep explicitly calls out the new stats/dashboard line plus the background stats daemon commands. Updated `docs/RELEASING.md` with the repo's 0-ver versioning policy and an explicit `--date` reminder after the changelog generator initially stamped `2026-03-20` from UTC instead of the intended local release date `2026-03-19`.
Ran `bun run changelog:build --version 0.7.0`, which generated `CHANGELOG.md` and `release/release-notes.md` and removed the queued `changes/*.md` fragments for the accumulated stats, launcher, overlay, JLPT, and stability work. Added a curated `v0.7.0` summary to `docs-site/changelog.md` so the public docs changelog stays aligned with the committed root changelog while remaining user-facing.
Verification:
- `bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh`
- `bun run changelog:lint`
- `bun run changelog:check --version 0.7.0`
- `bun run verify:config-example`
- `bun run typecheck`
- `bun run test:fast`
- `bun run test:env`
- `bun run build`
- `bun run docs:test`
- `bun run docs:build`
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Prepared minor release `v0.7.0` as the next 0-ver major line. Version metadata, root changelog, generated release notes, README release copy, release-guide policy, and the public docs changelog are now aligned for the release cut.
Docs update required: yes. Completed in `README.md`, `docs/RELEASING.md`, and `docs-site/changelog.md`.
Changelog fragment required: no new fragment for this task. Existing pending release fragments were consumed into the committed `v0.7.0` changelog section and `release/release-notes.md`.
Release-prep verification passed across changelog validation, config-example verification, typecheck, fast/env tests, full build, and docs-site test/build.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,65 +0,0 @@
---
id: TASK-177.1
title: Fix overview lookup rate metric
status: Done
assignee:
- '@codex'
created_date: '2026-03-19 17:46'
updated_date: '2026-03-23 03:22'
labels:
- stats
- immersion-tracking
- yomitan
dependencies: []
references:
- stats/src/components/overview/OverviewTab.tsx
- stats/src/lib/dashboard-data.ts
- stats/src/lib/yomitan-lookup.ts
- src/core/services/immersion-tracker/query.ts
- src/core/services/stats-server.ts
parent_task_id: TASK-177
priority: medium
ordinal: 132500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Update the stats homepage Tracking Snapshot so Lookup Rate reflects lifetime intentional Yomitan lookups normalized by total tokens seen, matching the newer stats semantics already used in session, media, and anime views.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Overview data exposes the lifetime totals needed to compute global Yomitan lookups per 100 tokens on the homepage
- [x] #2 The homepage Tracking Snapshot Lookup Rate card shows Yomitan lookup rate as `X / 100 tokens` with tooltip/copy aligned to that meaning
- [x] #3 Automated tests cover the lifetime totals plumbing and homepage summary/rendering change
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Extend overview lifetime hints/query plumbing to include total tokens seen and total intentional Yomitan lookups from finished sessions.
2. Add/adjust focused tests first for query hints, stats overview API typing/mocks, and overview summary formatting so the homepage metric fails under old semantics.
3. Update the overview summary/card to derive Lookup Rate from lifetime Yomitan lookups per 100 tokens and align tooltip/copy with that meaning.
4. Run focused verification on the touched query, stats-server, and stats UI tests; record results and blockers in the task notes.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Extended overview lifetime hints to include total tokens seen and total intentional Yomitan lookups from finished sessions so the homepage can compute a true global lookup rate.
Extracted the homepage Tracking Snapshot into a dedicated presentational component to keep OverviewTab smaller and make the Lookup Rate card copy directly testable.
Focused verification passed for query hints, IPC/stats overview plumbing, stats server overview response, dashboard summary logic, and homepage snapshot rendering.
SubMiner verifier core lane artifact: .tmp/skill-verification/subminer-verify-20260319-105320-7FDlwh. `bun run typecheck` passed there; `bun run test:fast` failed for a pre-existing/unrelated environment issue in scripts/update-aur-package.test.ts because scripts/update-aur-package.sh reported `mapfile: command not found`.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Homepage Lookup Rate now uses lifetime intentional Yomitan lookups normalized by lifetime tokens seen, matching the existing session/media/anime semantics instead of the old known-word hit-rate metric. I extended overview query hints and API typings with total token and Yomitan lookup totals, updated the overview summary builder to reuse the shared per-100-token formatter, and replaced the inline Tracking Snapshot block with a dedicated component that renders `X / 100 tokens` plus Yomitan-specific tooltip copy.
Tests added/updated: query hints coverage for the new lifetime totals, stats server and IPC overview fixtures, overview summary assertions, and a dedicated Tracking Snapshot render test for the homepage card text. Focused `bun test` runs passed for those touched areas. Repo-native verifier `--lane core` also passed `bun run typecheck`; its `bun run test:fast` step still fails for the unrelated existing `scripts/update-aur-package.sh: line 71: mapfile: command not found` environment issue.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,63 +0,0 @@
---
id: TASK-177.2
title: Count homepage new words by headword
status: Done
assignee:
- '@codex'
created_date: '2026-03-19 19:38'
updated_date: '2026-03-23 03:22'
labels:
- stats
- immersion-tracking
- vocabulary
dependencies: []
references:
- src/core/services/immersion-tracker/query.ts
- stats/src/components/overview/TrackingSnapshot.tsx
- stats/src/lib/dashboard-data.ts
parent_task_id: TASK-177
priority: medium
ordinal: 130500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Align the homepage New Words metric with the Known Words semantics by counting distinct headwords first seen in the selected window, so inflected or alternate forms of the same word do not inflate the summary.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Homepage new-word counts use distinct headwords by earliest first-seen timestamp instead of counting separate word-form rows
- [x] #2 Homepage tooltip/copy reflects the headword-based semantics
- [x] #3 Automated tests cover the headword de-duplication behavior and affected overview copy
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Change the new-word aggregate query to group `imm_words` by headword, compute each headword's earliest `first_seen`, and count headwords whose first sighting falls within today/week windows.
2. Add failing tests first for the aggregate path so multiple rows sharing a headword only contribute once.
3. Update homepage tooltip/copy to say unique headwords first seen today/week.
4. Run focused query and stats overview tests, then record verification and any blockers.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Updated the new-word aggregate to count distinct headwords by each headword's earliest `first_seen` timestamp, so multiple inflected/form rows for the same headword contribute only once.
Adjusted homepage tooltip copy to say unique headwords first seen today/week, keeping the visible card labels unchanged.
Focused verification passed for the query aggregate and homepage snapshot tests.
SubMiner verifier core lane artifact: .tmp/skill-verification/subminer-verify-20260319-123942-4intgW. `bun run typecheck` passed there; `bun run test:fast` still fails for the unrelated environment issue in scripts/update-aur-package.test.ts (`mapfile: command not found`).
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Homepage New Words now uses headword-level semantics instead of counting separate `(headword, word, reading)` rows. The aggregate query groups `imm_words` by headword, uses each headword's earliest `first_seen`, and counts headwords first seen today or this week so alternate forms do not inflate the summary. The homepage tooltip copy now explicitly says the metric is based on unique headwords.
Added focused regression coverage for the de-duplication rule in `getQueryHints` and for the updated homepage tooltip text. Targeted `bun test` runs passed for the touched query and stats UI files. Repo verifier `--lane core` again passed `bun run typecheck`; `bun run test:fast` remains blocked by the unrelated existing `scripts/update-aur-package.sh: line 71: mapfile: command not found` failure.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,65 +0,0 @@
---
id: TASK-177.3
title: Fix attached stats command flow and browser config
status: Done
assignee:
- '@codex'
created_date: '2026-03-19 20:15'
updated_date: '2026-03-23 03:22'
labels:
- launcher
- stats
- cli
dependencies: []
references:
- launcher/commands/stats-command.ts
- launcher/commands/command-modules.test.ts
- launcher/main.test.ts
- src/main/runtime/stats-cli-command.ts
- src/main/runtime/stats-cli-command.test.ts
parent_task_id: TASK-177
priority: medium
ordinal: 129500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Make `subminer stats` stay attached to the foreground app process instead of routing through daemon startup, while keeping background/stop behavior on the daemon path. Ensure browser opening for stats respects only `stats.autoOpenBrowser` in the normal stats flow.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Default `subminer stats` forwards through the attached foreground stats command path instead of the daemon-start path
- [x] #2 `subminer stats --background` and `subminer stats --stop` continue using the daemon control path
- [x] #3 Normal stats launches do not open a browser when `stats.autoOpenBrowser` is false, and automated tests cover the launcher/runtime regressions
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add failing launcher tests first so default `stats` expects `--stats` forwarding while `--background` and `--stop` continue to expect daemon control flags.
2. Add/adjust runtime stats command tests to prove `stats.autoOpenBrowser=false` suppresses browser opening on the normal attached stats path.
3. Patch launcher forwarding logic in `launcher/commands/stats-command.ts` to choose foreground vs daemon flags correctly without changing cleanup handling.
4. Run targeted launcher and stats runtime tests, then record verification results and blockers.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Confirmed root cause: launcher default `stats` flow always forwarded `--stats-daemon-start` plus `--stats-daemon-open-browser`, which detached the terminal process and bypassed `stats.autoOpenBrowser` because browser opening happened in daemon control instead of the normal stats CLI handler.
Updated launcher forwarding so plain `subminer stats` now uses the attached `--stats` path, while explicit `--background` and `--stop` continue using daemon control flags.
Added launcher regression coverage for the attached/default path and preserved background/stop expectations; added runtime coverage proving `stats.autoOpenBrowser=false` suppresses browser opening on the normal stats path.
Verifier passed for `launcher-plugin` and `runtime-compat` lanes. Artifact: .tmp/skill-verification/subminer-verify-20260319-131703-ZaAaUV.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed `subminer stats` so the default command now forwards to the normal attached `--stats` app path instead of the daemon-start path. That keeps the foreground process attached to the terminal as expected, while `subminer stats --background` and `subminer stats --stop` still use daemon control. Because the normal stats CLI path already respects `config.stats.autoOpenBrowser`, this also fixes the unwanted browser-open behavior that previously bypassed config via `--stats-daemon-open-browser`.
Added launcher command and launcher integration regressions for the new forwarding behavior, plus a runtime stats CLI regression that asserts `stats.autoOpenBrowser=false` suppresses browser opening. Verification passed with targeted launcher tests, targeted runtime stats tests, and the SubMiner verifier `launcher-plugin` + `runtime-compat` lanes.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,67 +0,0 @@
---
id: TASK-182.2
title: Improve session detail known-word chart scaling
status: Done
assignee:
- codex
created_date: '2026-03-19 20:31'
updated_date: '2026-03-23 03:22'
labels:
- bug
- stats
- ui
dependencies: []
references:
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/sessions/SessionDetail.tsx
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/lib/session-detail.test.tsx
parent_task_id: TASK-182
ordinal: 128500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Adjust the expanded session-detail known-word percentage chart so the vertical range reflects the session's actual percent range instead of always spanning 0-100. Keep the chart easier to read while preserving the percent-based tooltip/legend behavior already used in the stats UI.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Expanded session detail scales the known/unknown percent chart to the session's observed percent range instead of hard-coding a 0-100 top bound
- [x] #2 The chart keeps a small headroom above the highest observed known-word percent so the line remains visually readable near the top edge
- [x] #3 Automated frontend coverage locks the new percent-domain behavior and preserves existing session-detail rendering
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a focused frontend regression test for the session-detail ratio chart domain calculation, covering a session whose known-word percentage stays in a narrow band below 100% and expecting a dynamic top bound with headroom.
2. Update `stats/src/components/sessions/SessionDetail.tsx` to compute a dynamic percent-axis domain and matching ticks for the ratio chart, keeping the lower bound at 0%, adding modest padding above the highest known percentage, rounding to clean tick steps, and capping at 100%.
3. Apply the computed percent-axis bounds consistently to the right-side Y axis and the session chart pause overlays so the visual framing stays aligned.
4. Run targeted frontend tests and the SubMiner verification helper on the touched files, then record results and any blockers in the task.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Implemented dynamic known-percentage axis scaling in `stats/src/components/sessions/SessionDetail.tsx`: the ratio chart now keeps a 0% floor, uses the highest observed known percentage plus 5 points of headroom for the top bound, rounds that bound up to clean 10-point ticks, caps at 100%, and enables `allowDataOverflow` so the stacked area chart actually honors the tighter domain.
Added frontend regression coverage in `stats/src/lib/session-detail.test.tsx` for the axis-max helper, covering both a narrow-band session and near-100% cap behavior.
Added user-visible changelog fragment `changes/2026-03-19-session-detail-chart-scaling.md`.
Verification: `bun test stats/src/lib/session-detail.test.tsx` passed; `bun run typecheck` passed; `bun run changelog:lint` passed; `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane core stats/src/components/sessions/SessionDetail.tsx stats/src/lib/session-detail.test.tsx` ran and passed `typecheck` but failed `bun run test:fast` on a pre-existing unrelated issue in `scripts/update-aur-package.test.ts` / `scripts/update-aur-package.sh` (`mapfile: command not found`). Artifacts: `.tmp/skill-verification/subminer-verify-20260319-134440-JRHAUJ`.
Docs decision: no internal docs update required; the behavior change is localized UI presentation with no API/workflow change. Changelog decision: yes, required and completed because the fix is user-visible.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Improved expanded session-detail chart readability by replacing the fixed 0-100 known-word percentage axis with a dynamic top bound based on the sessions highest observed known percentage plus modest headroom, rounded to clean ticks and capped at 100%. The ratio chart now also enables `allowDataOverflow` so Recharts preserves the tighter percent domain even though the stacked known/unknown areas sum to 100%.
Added frontend regression coverage for the new axis-max behavior and a changelog fragment for the user-visible stats fix.
Verification: `bun test stats/src/lib/session-detail.test.tsx`, `bun run typecheck`, and `bun run changelog:lint` passed. The SubMiner verification helpers `core` lane also passed `typecheck`, but `bun run test:fast` remains red on a pre-existing unrelated bash-compat failure in `scripts/update-aur-package.test.ts` / `scripts/update-aur-package.sh` (`mapfile: command not found`).
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,68 +0,0 @@
---
id: TASK-192
title: Fix stale anime cover art after AniList reassignment
status: Done
assignee:
- codex
created_date: '2026-03-20 00:12'
updated_date: '2026-03-23 03:22'
labels:
- stats
- immersion-tracker
- anilist
milestone: m-1
dependencies: []
references:
- src/core/services/immersion-tracker-service.ts
- src/core/services/immersion-tracker/query.ts
- src/core/services/immersion-tracker-service.test.ts
priority: medium
ordinal: 127500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Fix the stats anime-detail cover image path so reassigning an anime to a different AniList entry replaces the stored cover art bytes instead of keeping the previous image blob under updated metadata.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Reassigning an anime to a different AniList entry stores the new cover art bytes for that anime's videos
- [x] #2 Shared blob deduplication still works when multiple videos in the anime use the same new cover image
- [x] #3 Focused regression coverage proves stale cover blobs are replaced on reassignment
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a failing regression test that reassigns an anime twice with different downloaded cover bytes and asserts the resolved cover updates.
2. Update cover-art upsert logic so new blob bytes generate a new shared hash instead of reusing an existing hash for the row.
3. Run the focused immersion tracker service test file and record the result.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
2026-03-20: Created during live debugging of a user-reported stale anime profile picture after changing the AniList entry from the stats UI.
2026-03-20: Root cause was in `upsertCoverArt(...)`. When a row already had `cover_blob_hash`, a later AniList reassignment with a freshly downloaded cover reused the existing hash instead of hashing the new bytes, so the blob store kept serving the old image while metadata changed.
2026-03-20: Added a regression in `src/core/services/immersion-tracker-service.test.ts` that reassigns the same anime twice with different fetched image bytes and asserts the resolved anime cover changes to the second blob while both videos still deduplicate to one shared hash.
2026-03-20: Fixed `src/core/services/immersion-tracker/query.ts` so incoming cover blob bytes compute a fresh hash before falling back to an existing row hash. Existing hashes are now reused only when no new bytes were fetched.
2026-03-20: Verification commands run:
- `bun test src/core/services/immersion-tracker-service.test.ts`
- `bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh src/core/services/immersion-tracker/query.ts src/core/services/immersion-tracker-service.test.ts`
- `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane core src/core/services/immersion-tracker/query.ts src/core/services/immersion-tracker-service.test.ts`
2026-03-20: Verification results:
- focused service test: passed
- verifier lane selection: `core`
- verifier result: passed (`bun run typecheck`, `bun run test:fast`)
- verifier artifacts: `.tmp/skill-verification/subminer-verify-20260320-001433-IZLFqs/`
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed stale anime cover art after AniList reassignment by correcting cover-blob hash replacement in the immersion tracker storage layer. Reassignments now store the new fetched image bytes instead of reusing the previous blob hash from the row, while still deduplicating the updated image across videos in the same anime.
Added focused regression coverage that reproduces the exact failure mode: same anime reassigned twice with different cover downloads, with the second image expected to replace the first. Verified with the touched service test file plus the SubMiner `core` verification lane.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,35 +0,0 @@
---
id: TASK-194
title: App-owned YouTube subtitle picker flow
status: Done
assignee: []
created_date: '2026-03-18 07:52'
updated_date: '2026-03-23 03:22'
labels: []
dependencies: []
references:
- /home/sudacode/projects/japanese/SubMiner/launcher/youtube/orchestrator.ts
- /home/sudacode/projects/japanese/SubMiner/launcher/youtube/manual-subs.ts
- /home/sudacode/projects/japanese/SubMiner/src/core/services/tokenizer.ts
documentation:
- /home/sudacode/projects/japanese/SubMiner/youtube.md
priority: medium
ordinal: 137500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Replace the YouTube subtitle-generation-first flow with an app-owned picker flow that boots mpv paused, opens an overlay track picker, downloads selected subtitles into external subtitle files, and preserves generation as an explicit mode. Keep the existing SubMiner tokenization and annotation pipeline as the downstream consumer of downloaded subtitle files.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Launcher and app expose YouTube subtitle acquisition modes `download` and `generate`, with `download` as the default.
- [x] #2 YouTube playback boots mpv paused and presents an overlay selection UI for primary and secondary subtitle choices.
- [x] #3 Selected YouTube subtitle tracks are downloaded to external subtitle files and loaded into mpv before playback resumes.
- [x] #4 `generate` mode preserves the existing subtitle generation path as an explicit opt-in behavior.
- [x] #5 Downloaded YouTube subtitle files integrate with the existing SubMiner subtitle/tokenization/annotation pipeline without regressing current overlay behavior.
- [x] #6 Tests cover mode selection, subtitle-track enumeration/selection flow, and the paused bootstrap plus app handoff path.
- [x] #7 User-facing config and launcher docs are updated to describe the new modes and default behavior.
<!-- AC:END -->

View File

@@ -1,92 +0,0 @@
---
id: TASK-200
title: 'Address latest PR #19 CodeRabbit follow-ups'
status: Done
assignee:
- '@codex'
created_date: '2026-03-19 07:18'
updated_date: '2026-03-23 03:22'
labels:
- pr-review
- anki-integration
- launcher
milestone: m-1
dependencies: []
references:
- launcher/mpv.test.ts
- src/anki-integration.ts
- src/anki-integration/card-creation.ts
- src/anki-integration/runtime.ts
- src/anki-integration/known-word-cache.ts
priority: medium
ordinal: 133500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Validate the latest 2026-03-19 CodeRabbit review round on PR #19, implement only the confirmed fixes, and verify the touched launcher and Anki integration paths.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Each latest-round PR #19 CodeRabbit inline comment is validated against the current branch and classified as actionable or not warranted
- [x] #2 Confirmed correctness issues in launcher and Anki integration code are fixed with focused regression coverage where practical
- [x] #3 Targeted verification runs for the touched areas and the task notes record what changed versus what was rejected
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Validate the five inline comments from the 2026-03-19 CodeRabbit PR #19 review against current launcher and Anki integration code.
2. Add or extend focused tests for any confirmed launcher env-sandbox, notification-state, AVIF lead-in propagation, or known-word-cache lifecycle/scope regressions.
3. Apply the smallest safe fixes in `launcher/mpv.test.ts`, `src/anki-integration.ts`, `src/anki-integration/card-creation.ts`, `src/anki-integration/runtime.ts`, and `src/anki-integration/known-word-cache.ts` as needed.
4. Run targeted unit tests plus the SubMiner verification helper on the touched files, then record which comments were accepted or rejected in task notes.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Validated the five latest inline comments from CodeRabbit review `3973222927` on PR #19.
Accepted fixes:
- Hardened the three `findAppBinary` launcher tests against host leakage by sandboxing `SUBMINER_APPIMAGE_PATH` / `SUBMINER_BINARY_PATH` and stubbing executable checks so `/opt` and PATH resolution are deterministic.
- `showNotification()` now marks OSD/both updates as failed when `errorSuffix` is present instead of always rendering a success marker.
- `applyRuntimeConfigPatch()` now avoids starting or stopping known-word cache lifecycle work while the runtime is stopped, while still clearing cached state when highlighting is disabled.
- Extracted shared known-word cache lifecycle helpers and switched the persisted cache identity to the same lifecycle config used by runtime restart detection, so changes to `fields.word`, per-deck field mappings, or refresh interval invalidate stale cache state correctly.
Rejected fix:
- The `createSentenceCard()` AVIF lead-in comment was technically incomplete for this branch. There is no current caller that computes an `animatedLeadInSeconds` input for sentence-card creation, and the existing lead-in resolver depends on note media fields that do not exist before the new card's media is generated.
Regression coverage added:
- `src/anki-integration.test.ts` partial-failure OSD result marker.
- `src/anki-integration/runtime.test.ts` stopped-runtime known-word lifecycle guards.
- `src/anki-integration/known-word-cache.test.ts` cache invalidation when `fields.word` or per-deck field mappings change.
Verification:
- `bun test src/anki-integration/runtime.test.ts`
- `bun test src/anki-integration/known-word-cache.test.ts`
- `bun test src/anki-integration.test.ts --test-name-pattern 'marks partial update notifications as failures in OSD mode'`
- `bun test launcher/mpv.test.ts --test-name-pattern 'findAppBinary resolves ~/.local/bin/SubMiner.AppImage when it exists|findAppBinary resolves /opt/SubMiner/SubMiner.AppImage when ~/.local/bin candidate does not exist|findAppBinary finds subminer on PATH when AppImage candidates do not exist'`
- `bun test src/anki-integration.test.ts src/anki-integration/runtime.test.ts src/anki-integration/known-word-cache.test.ts launcher/mpv.test.ts`
- `bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh launcher/mpv.test.ts src/anki-integration.ts src/anki-integration/runtime.ts src/anki-integration/known-word-cache.ts src/anki-integration/runtime.test.ts src/anki-integration/known-word-cache.test.ts src/anki-integration.test.ts`
- `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane launcher-plugin --lane core launcher/mpv.test.ts src/anki-integration.ts src/anki-integration/runtime.ts src/anki-integration/known-word-cache.ts src/anki-integration/runtime.test.ts src/anki-integration/known-word-cache.test.ts src/anki-integration.test.ts`
Verifier result:
- `launcher-plugin` lane passed (`test:launcher:smoke:src`, `test:plugin:src`).
- `core/typecheck` passed.
- `core/test-fast` failed for an unrelated existing environment issue in `scripts/update-aur-package.test.ts`: `scripts/update-aur-package.sh: line 71: mapfile: command not found` under the local macOS Bash environment.
- Verifier artifacts: `.tmp/skill-verification/subminer-verify-20260319-002617-UgpKUy`
Classification: actionable and fixed -> `launcher/mpv.test.ts` env leakage hardening, `src/anki-integration.ts` partial-failure OSD marker, `src/anki-integration/runtime.ts` started-guard for known-word lifecycle calls, `src/anki-integration/known-word-cache.ts` cache identity alignment with runtime lifecycle config.
Classification: not warranted as written -> `src/anki-integration/card-creation.ts` lead-in threading comment. No current `createSentenceCard()` caller computes or owns an `animatedLeadInSeconds` value, and the existing lead-in helper derives from preexisting note media fields, so blindly adding an optional parameter would not fix a real branch behavior bug.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed four confirmed PR #19 latest-round CodeRabbit issues locally: deterministic launcher `findAppBinary` tests, correct partial-failure OSD result markers, started-state guards around known-word cache lifecycle restarts, and shared known-word cache identity logic so field-mapping changes invalidate stale cache state. Added focused regression coverage for each confirmed behavior.
One comment was intentionally not applied: the `createSentenceCard()` AVIF lead-in suggestion does not match the current branch architecture because no caller computes that value today and the existing resolver requires preexisting note media fields. Verification is green for all touched targeted tests plus the launcher-plugin/core typecheck lanes; the only remaining red is an unrelated existing `test:fast` failure in `scripts/update-aur-package.test.ts` caused by `mapfile` being unavailable in the local Bash environment.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,67 +0,0 @@
---
id: TASK-201
title: Suppress repeated macOS overlay loading OSD during fullscreen tracker flaps
status: Done
assignee:
- '@codex'
created_date: '2026-03-19 18:47'
updated_date: '2026-03-23 03:22'
labels:
- bug
- macos
- overlay
dependencies: []
references:
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/overlay-visibility.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/main/overlay-visibility-runtime.ts
- /Users/sudacode/projects/japanese/SubMiner/src/main/state.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/overlay-visibility.test.ts
priority: high
ordinal: 131500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Reduce macOS fullscreen annoyance where the visible overlay briefly loses tracking and re-shows the `Overlay loading...` OSD even though the overlay runtime is already initialized and no new instance is launching. Keep the first startup/loading feedback, but suppress repeat loading notifications caused by subsequent tracker churn during fullscreen enter/leave or focus flaps.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 The first macOS visible-overlay load still shows the existing `Overlay loading...` OSD when tracker data is not yet ready.
- [x] #2 Repeated macOS tracker flaps after the overlay has already recovered do not immediately re-show `Overlay loading...` on every loss/recovery cycle.
- [x] #3 Focused regression tests cover the repeated tracker-loss/recovery path and preserve the initial-load notification behavior.
- [x] #4 The change does not alter overlay runtime bootstrap or single-instance behavior; only notification suppression behavior changes.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add focused failing regressions in `src/core/services/overlay-visibility.test.ts` that preserve the first macOS `Overlay loading...` OSD and suppress an immediate second OSD after tracker recovery/loss churn.
2. Extend the overlay-visibility state/runtime plumbing with a small macOS loading-OSD suppression state so tracker flap retries can be rate-limited without touching overlay bootstrap or single-instance logic.
3. Reset the suppression when the user explicitly hides the visible overlay so intentional hide/show retries can still surface first-load feedback.
4. Run focused verification for the touched overlay visibility/runtime tests and update the task with results.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Added optional loading-OSD suppression hooks to `src/core/services/overlay-visibility.ts` so macOS can rate-limit repeated `Overlay loading...` notifications without changing overlay bootstrap behavior.
Implemented service-local suppression state in `src/main/overlay-visibility-runtime.ts` with a 30s cooldown and explicit reset when the visible overlay is manually hidden, so fullscreen tracker flaps stay quiet but intentional hide/show retries can still show loading feedback.
Added focused regressions in `src/core/services/overlay-visibility.test.ts` for `loss -> recover -> immediate loss` suppression and for manual hide resetting suppression.
Verification: `bun test src/core/services/overlay-visibility.test.ts`; `bun test src/main/runtime/overlay-visibility-runtime-main-deps.test.ts src/main/runtime/overlay-visibility-runtime.test.ts`; `bun run typecheck`; `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane runtime-compat src/core/services/overlay-visibility.ts src/main/overlay-visibility-runtime.ts src/core/services/overlay-visibility.test.ts` -> passed. Real-runtime lane skipped: change is notification suppression logic and cheap/runtime-compat coverage was sufficient for this scoped behavior change; no live mpv/macOS fullscreen session was run in this turn.
Docs update required: no. Changelog fragment required: yes; added `changes/2026-03-19-overlay-loading-osd-fullscreen-flaps.md`.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Reduced repeated macOS `Overlay loading...` popups caused by fullscreen tracker flap churn without touching overlay bootstrap or single-instance behavior. `src/core/services/overlay-visibility.ts` now accepts optional suppression hooks around the loading OSD path, and `src/main/overlay-visibility-runtime.ts` uses service-local state to rate-limit that OSD for 30 seconds while resetting the suppression when the visible overlay is explicitly hidden. Added focused regressions in `src/core/services/overlay-visibility.test.ts` to preserve the first-load notification, suppress immediate repeat notifications after tracker recovery/loss churn, and keep manual hide/show retries able to surface the loading OSD again. Added changelog fragment `changes/2026-03-19-overlay-loading-osd-fullscreen-flaps.md`. Verification passed with targeted overlay tests, typecheck, and the `runtime-compat` verifier lane; live macOS/mpv fullscreen runtime validation was not run in this turn.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,70 +0,0 @@
---
id: TASK-202
title: Use ended session media position for anime episode progress
status: Done
assignee:
- Codex
created_date: '2026-03-19 14:55'
updated_date: '2026-03-19 17:36'
labels:
- stats
- ui
- bug
milestone: m-1
dependencies: []
references:
- stats/src/components/anime/EpisodeList.tsx
- stats/src/types/stats.ts
- src/core/services/immersion-tracker/session.ts
- src/core/services/immersion-tracker/query.ts
- src/core/services/immersion-tracker/storage.ts
priority: medium
ordinal: 105720
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
The anime episode list currently computes the `Progress` column from cumulative `totalActiveMs / durationMs`, which can exceed the intended watch-position meaning after rewatches or repeated sessions. Persist the playback position at the time a session ends and drive episode progress from that stored stop position instead.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Session finalization persists the playback position reached when the session ended.
- [x] #2 Anime episode queries expose the most recent ended-session media position for each episode.
- [x] #3 Episode-list progress renders from ended media position instead of cumulative active watch time.
- [x] #4 Regression coverage locks storage/query/UI behavior for the new progress source.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add failing regression coverage for persisted ended media position and episode progress rendering.
2. Add `ended_media_ms` to the immersion-session schema and persist `lastMediaMs` when ending a session.
3. Thread the new field through episode queries/types and render episode progress from `endedMediaMs / durationMs`.
4. Run targeted verification plus typecheck, then record the outcome.
<!-- SECTION:PLAN:END -->
## Outcome
<!-- SECTION:OUTCOME:BEGIN -->
Added nullable `ended_media_ms` storage to immersion sessions, persisted `lastMediaMs` when sessions finalize, and exposed the most recent ended-session media position through anime episode queries/types. The anime episode list now renders `Progress` from `endedMediaMs / durationMs` instead of cumulative active watch time, so rewatches no longer inflate the displayed percentage.
Verification:
- `bun test src/core/services/immersion-tracker/storage-session.test.ts`
- `bun test src/core/services/immersion-tracker/__tests__/query.test.ts`
- `bun test stats/src/lib/yomitan-lookup.test.tsx stats/src/lib/stats-ui-navigation.test.tsx`
- `bun run typecheck`
- `bun run changelog:lint`
- `bun x prettier --check 'src/core/services/immersion-tracker/types.ts' 'src/core/services/immersion-tracker/storage.ts' 'src/core/services/immersion-tracker/session.ts' 'src/core/services/immersion-tracker/query.ts' 'src/core/services/immersion-tracker/storage-session.test.ts' 'src/core/services/immersion-tracker/__tests__/query.test.ts' 'stats/src/types/stats.ts' 'stats/src/components/anime/EpisodeList.tsx' 'stats/src/lib/yomitan-lookup.test.tsx' 'stats/src/lib/stats-ui-navigation.test.tsx' 'backlog/tasks/task-202 - Use-ended-session-media-position-for-anime-episode-progress.md' 'changes/2026-03-19-stats-ended-media-progress.md'`
- `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane core 'src/core/services/immersion-tracker/types.ts' 'src/core/services/immersion-tracker/storage.ts' 'src/core/services/immersion-tracker/session.ts' 'src/core/services/immersion-tracker/query.ts' 'src/core/services/immersion-tracker/storage-session.test.ts' 'src/core/services/immersion-tracker/__tests__/query.test.ts' 'stats/src/types/stats.ts' 'stats/src/components/anime/EpisodeList.tsx' 'stats/src/lib/yomitan-lookup.test.tsx' 'stats/src/lib/stats-ui-navigation.test.tsx' 'backlog/tasks/task-202 - Use-ended-session-media-position-for-anime-episode-progress.md' 'changes/2026-03-19-stats-ended-media-progress.md'`
- Verifier artifacts: `.tmp/skill-verification/subminer-verify-20260319-173511-AV7kUg/`
<!-- SECTION:OUTCOME:END -->

View File

@@ -1,47 +0,0 @@
---
id: TASK-203
title: Restore known and JLPT annotation for reading-mismatch subtitle tokens
status: Done
assignee:
- Codex
created_date: '2026-03-19 18:25'
updated_date: '2026-03-19 18:25'
labels:
- subtitle
- bug
dependencies: []
references:
- src/core/services/tokenizer/annotation-stage.ts
- src/core/services/tokenizer/annotation-stage.test.ts
priority: medium
ordinal: 105721
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Some subtitle tokens lose both known-word coloring and JLPT underline even though the popup resolves a valid dictionary term. Repro example: `大体` in `大体 僕だって困ってたんですよ!` can be known via kana-only Anki data (`だいたい`) while JLPT lookup should still resolve from the kanji surface/headword.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Subtitle annotation can mark a token known via its reading when the configured headword/surface lookup misses.
- [x] #2 JLPT eligibility no longer drops valid kanji terms just because their reading contains repeated kana patterns.
- [x] #3 Regression coverage locks the combined known + JLPT case for `大体`.
<!-- AC:END -->
## Outcome
<!-- SECTION:OUTCOME:BEGIN -->
Known-word annotation now falls back to the token reading after the configured headword/surface lookup misses, so kana-only known-card entries still light up matching subtitle tokens. JLPT eligibility now ignores repeated-kana noise checks on the reading when a real surface/headword is present, which preserves JLPT tagging for words like `大体`.
Verification:
- `bun test src/core/services/tokenizer/annotation-stage.test.ts`
<!-- SECTION:OUTCOME:END -->

View File

@@ -1,60 +0,0 @@
---
id: TASK-204
title: Make known-word cache incremental and avoid full rebuilds
status: Done
assignee:
- Codex
created_date: '2026-03-19 19:05'
updated_date: '2026-03-19 19:12'
labels:
- anki
- cache
- performance
dependencies: []
references:
- src/anki-integration/known-word-cache.ts
- src/anki-integration.ts
- src/config/resolve/anki-connect.ts
- src/config/definitions/defaults-integrations.ts
priority: high
ordinal: 105722
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Replace the known-word cache rebuild behavior with incremental synchronization. Startup should load existing cache state without immediately pulling all tracked Anki notes. Config-timed sync should reconcile adds, deletes, and in-place field edits against cached per-note state. Mined cards should optionally append their extracted words immediately after mining, enabled by default. Full rebuild should remain available only through explicit doctor tooling.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Known-word cache startup no longer performs an automatic full rebuild.
- [x] #2 Config-timed sync incrementally reconciles note additions, deletions, and edited word fields for the tracked known-word deck scope.
- [x] #3 Newly mined cards update the known-word cache immediately when the new config flag is enabled, and skip that fast path when disabled.
- [x] #4 Persisted cache state remains usable by stats endpoints that read the `words` set from disk.
- [x] #5 Regression tests cover startup behavior, incremental sync diffs, and the new config flag.
<!-- AC:END -->
## Outcome
<!-- SECTION:OUTCOME:BEGIN -->
Known-word cache startup now loads persisted state and schedules sync based on refresh timing instead of wiping and rebuilding immediately. Persisted cache state now includes per-note word snapshots so timed refreshes can remove deleted notes, update edited notes, and keep the global `words` set stable for stats consumers. Added `ankiConnect.knownWords.addMinedWordsImmediately`, default `true`, so newly mined cards can update the cache immediately without waiting for the next timed sync.
Verification:
- `bun test src/anki-integration/known-word-cache.test.ts`
- `bun test src/config/resolve/anki-connect.test.ts src/config/config.test.ts`
- `bun test src/anki-integration.test.ts src/anki-integration/runtime.test.ts src/core/services/__tests__/stats-server.test.ts`
- `bun run test:config:src`
- `bun run typecheck`
- `bun run test:fast`
- `bun run test:env`
- `bun run build`
- `bun run test:smoke:dist`
<!-- SECTION:OUTCOME:END -->

View File

@@ -1,54 +0,0 @@
---
id: TASK-204.1
title: Restore stale-only startup known-word cache refresh
status: Done
assignee:
- '@Codex'
created_date: '2026-03-20 02:52'
updated_date: '2026-03-23 03:22'
labels:
- anki
- cache
- bug
dependencies: []
references:
- src/anki-integration/known-word-cache.ts
- src/anki-integration/known-word-cache.test.ts
- docs/plans/2026-03-19-known-word-cache-incremental-sync-design.md
parent_task_id: TASK-204
priority: high
ordinal: 124500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Follow up on the incremental known-word cache change so startup still performs a refresh when the persisted cache is older than the configured refresh interval, while leaving fresh persisted state untouched.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Startup refreshes known words immediately when persisted cache state is stale for the configured interval.
- [x] #2 Startup skips the immediate refresh when persisted cache state is still fresh.
- [x] #3 Regression tests cover both stale and fresh startup paths.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add focused known-word cache lifecycle tests that distinguish fresh startup state from stale startup state and verify the stale path currently fails.
2. Update startup scheduling in src/anki-integration/known-word-cache.ts so persisted cache still loads immediately, but startup only triggers an immediate refresh when the cache is stale for the configured interval or the cache scope/config changed.
3. Run focused known-word cache tests and targeted SubMiner verification for the touched cache/runtime lane, then update the task with results.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Verified current lifecycle behavior: fresh persisted known-word cache already skips immediate startup refresh when the cache scope/config matches; stale persisted cache already refreshes immediately. Added regression coverage for both startup paths plus a proxy integration test showing addNote responses return without waiting for background enrichment.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Added regression coverage for known-word cache startup behavior and proxy response timing. The cache tests now lock in the intended lifecycle: fresh persisted state stays load-only on startup, while stale persisted state refreshes immediately. Added a proxy integration test proving addNote responses return without waiting for background enrichment. Verification: targeted Bun tests passed (`bun test src/anki-connect.test.ts src/anki-integration/anki-connect-proxy.test.ts src/anki-integration/known-word-cache.test.ts src/anki-integration/note-update-workflow.test.ts src/anki-integration/runtime.test.ts`) and direct `bun run test:fast` passed. The `subminer-change-verification` helper repeatedly reported `bun run test:fast` as failed in its isolated lane despite the direct command passing, so that helper lane remains a flaky/blocking verification artifact rather than a reproduced code failure.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,63 +0,0 @@
---
id: TASK-205
title: 'Address PR #19 Claude frontend review follow-ups'
status: Done
assignee:
- codex
created_date: '2026-03-20 02:41'
updated_date: '2026-03-23 03:22'
labels: []
milestone: m-1
dependencies: []
references:
- stats/src/components/vocabulary/VocabularyTab.tsx
- stats/src/hooks/useSessions.ts
- stats/src/hooks/useTrends.ts
priority: medium
ordinal: 126500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Assess Claude's latest PR #19 review, apply any valid frontend fixes from that review batch, and verify the stats dashboard behavior stays unchanged aside from the targeted performance and error-handling improvements.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 VocabularyTab avoids recomputing expensive known-word and summary aggregates on unrelated rerenders while preserving current displayed values.
- [x] #2 useSessions and useSessionDetail normalize rejected values into stable string errors without throwing from the catch handler.
- [x] #3 Targeted tests cover the addressed review items and pass locally.
- [x] #4 Any user-facing docs remain accurate after the changes.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add focused tests that fail on the current branch for the two valid Claude findings: render-time aggregate recomputation in VocabularyTab and unsafe non-Error rejection handling in useSessions/useSessionDetail.
2. Update VocabularyTab to memoize the expensive summary and known-word aggregate calculations off the existing filteredWords/kanji/knownWords inputs without changing rendered values.
3. Normalize hook error handling to convert unknown rejection values into stable strings, matching the existing useTrends pattern.
4. Run the targeted stats/frontend test lane, verify no docs changes are needed, and record results in task notes.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Validated Claude's latest PR #19 review comment from 2026-03-20 and narrowed it to two valid frontend follow-ups: memoized VocabularyTab aggregates and non-Error-safe session hook error handling.
Added focused regression tests in stats/src/lib/vocabulary-tab.test.ts and stats/src/hooks/useSessions.test.ts before patching the implementation.
Verification: `cd stats && bun test src/lib/vocabulary-tab.test.ts src/hooks/useSessions.test.ts` passed; `bun run format:check:stats` passed.
Project-native verifier (`.agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane core ...`) passed root `bun run typecheck` and failed at `bun run test:fast` due an unrelated existing failure in `scripts/update-aur-package.test.ts` (`mapfile: command not found`). Artifact: `.tmp/skill-verification/subminer-verify-20260319-194525-vxVD9V`.
No user-facing docs changes were needed because the fixes only affect render-time memoization and error normalization.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Assessed Claude's latest PR #19 review and applied the two valid follow-ups. `stats/src/components/vocabulary/VocabularyTab.tsx` now memoizes `buildVocabularySummary(filteredWords, kanji)` and the known-word count so unrelated rerenders do not rescan the filtered vocabulary list. `stats/src/hooks/useSessions.ts` now exports a small `toErrorMessage` helper and uses it in both `useSessions` and `useSessionDetail`, preventing `.catch()` handlers from throwing when a promise rejects with a non-`Error` value.
Added targeted regressions in `stats/src/lib/vocabulary-tab.test.ts` and `stats/src/hooks/useSessions.test.ts` to lock in the memoization shape and error normalization behavior. Verification passed for `cd stats && bun test src/lib/vocabulary-tab.test.ts src/hooks/useSessions.test.ts` and `bun run format:check:stats`. The repo-native verification wrapper for the classified `core` lane also passed root `bun run typecheck`, but `bun run test:fast` is currently blocked by an unrelated existing failure in `scripts/update-aur-package.test.ts` (`mapfile: command not found`); artifacts are recorded under `.tmp/skill-verification/subminer-verify-20260319-194525-vxVD9V`.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,81 +0,0 @@
---
id: TASK-206
title: 'Assess latest PR #19 CodeRabbit review comments'
status: Done
assignee:
- '@codex'
created_date: '2026-03-20 02:51'
updated_date: '2026-03-23 03:22'
labels:
- pr-review
- launcher
- anki-integration
- docs
milestone: m-1
dependencies: []
references:
- launcher/commands/command-modules.test.ts
- launcher/commands/stats-command.ts
- launcher/config/cli-parser-builder.ts
- launcher/mpv.ts
- README.md
- src/anki-integration.ts
- src/anki-integration/known-word-cache.ts
priority: medium
ordinal: 125500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Validate the latest 2026-03-20 CodeRabbit review round on PR #19 against the current branch, implement only the confirmed fixes, and record which bot suggestions are stale, incorrect, or incomplete.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Each latest-round 2026-03-20 CodeRabbit inline comment on PR #19 is validated against current branch behavior and classified as actionable or not warranted
- [x] #2 Confirmed correctness issues in launcher, Anki integration, and docs are fixed with focused regression coverage where practical
- [x] #3 Targeted verification runs for the touched areas succeed or remaining unrelated failures are documented in task notes
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Pull the 2026-03-20 CodeRabbit review threads from PR #19 and validate each comment against the current branch, separating real issues from stale or incomplete bot guidance.
2. For each confirmed behavior bug, add or extend a focused failing test before changing production code; keep docs-only fixes scoped to the exact markdownlint/install issue.
3. Patch the smallest safe fixes in launcher, README, and Anki integration code, taking care not to overwrite unrelated local edits.
4. Run targeted tests and relevant SubMiner verification lanes for touched files, then record accepted versus rejected review comments in task notes and summary.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Validated the 2026-03-20 CodeRabbit PR #19 round as eight actionable items: one launcher test-name mismatch, three launcher behavior/test fixes, two README markdown/install fixes, one dead-code cleanup in Anki integration, and one real known-word cache deck-scoping bug.
Known-word cache review comment was correct in substance but needed a branch-specific fix: preserve deck->field scoping by querying per deck and carrying the allowed field list per note, rather than changing `notesInfo` shape.
Verification passed for targeted tests plus verifier docs/launcher-plugin lanes. Core verifier failed on unrelated pre-existing typecheck worktree state in `src/anki-integration/anki-connect-proxy.test.ts` (`TS2349` at line 395, `releaseProcessing?.()`), which is outside this task's touched files.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Assessed the latest 2026-03-20 CodeRabbit review round on PR #19 and applied all eight confirmed action items. Launcher behavior now surfaces non-zero stats-process exits after the startup handshake, rejects cleanup-only stats flags unless `cleanup` is selected, preserves empty quoted `mpv` args, and has updated regression coverage for each case. The known-word cache now preserves deck-specific field mappings during refresh by querying configured decks separately and extracting only the fields assigned to each deck; the unused `getPreferredWordValue` wrapper in `src/anki-integration.ts` was removed.
Documentation/test hygiene fixes also landed: the README platform badge no longer has an empty link target, Linux AppImage install instructions create `~/.local/bin` before downloads, the stats-command timing test was renamed to match actual behavior, and `launcher/picker.test.ts` now restores `XDG_DATA_HOME` safely while forcing Linux-path expectations explicitly so the file passes on macOS hosts.
Verification run:
- `bun test launcher/commands/command-modules.test.ts`
- `bun test launcher/parse-args.test.ts`
- `bun test launcher/mpv.test.ts`
- `bun test launcher/picker.test.ts`
- `bun test src/anki-integration/known-word-cache.test.ts`
- `bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh README.md launcher/commands/command-modules.test.ts launcher/commands/stats-command.ts launcher/config/cli-parser-builder.ts launcher/mpv.test.ts launcher/mpv.ts launcher/parse-args.test.ts launcher/picker.test.ts src/anki-integration.ts src/anki-integration/known-word-cache.test.ts src/anki-integration/known-word-cache.ts`
- `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane docs --lane launcher-plugin --lane core README.md launcher/commands/command-modules.test.ts launcher/commands/stats-command.ts launcher/config/cli-parser-builder.ts launcher/mpv.test.ts launcher/mpv.ts launcher/parse-args.test.ts launcher/picker.test.ts src/anki-integration.ts src/anki-integration/known-word-cache.test.ts src/anki-integration/known-word-cache.ts`
Verifier results:
- `docs` lane passed (`docs:test`, `docs:build`)
- `launcher-plugin` lane passed (`test:launcher:smoke:src`, `test:plugin:src`)
- `core/typecheck` failed on unrelated existing worktree changes in `src/anki-integration/anki-connect-proxy.test.ts(395,5)`: `TS2349 This expression is not callable. Type 'never' has no call signatures.`
- Verifier artifacts: `.tmp/skill-verification/subminer-verify-20260319-195752-RNLVgE`
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,68 +0,0 @@
---
id: TASK-207
title: 'Verify PR #19 follow-up typecheck blocker is cleared'
status: Done
assignee:
- '@codex'
created_date: '2026-03-20 03:03'
updated_date: '2026-03-23 03:22'
labels:
- pr-review
- anki-integration
- verification
milestone: m-1
dependencies: []
references:
- src/anki-integration/anki-connect-proxy.test.ts
priority: medium
ordinal: 123500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Confirm the previously unrelated `anki-connect-proxy.test.ts` typecheck failure no longer blocks verification for the PR #19 CodeRabbit follow-up work, and only patch it if the failure still reproduces.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Reproduce or clear the `src/anki-integration/anki-connect-proxy.test.ts` typecheck blocker with current workspace state
- [x] #2 If the blocker still exists, apply the smallest safe fix and verify it
- [x] #3 Document the verification result and any remaining unrelated blockers
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Re-run `bun run typecheck` and a focused proxy test against the current workspace to confirm whether the previous `anki-connect-proxy.test.ts` failure still reproduces.
2. If the failure reproduces, use the typecheck failure itself as the red test, patch the smallest type-safe fix in the test, and rerun focused verification.
3. Re-run the relevant verifier lane(s), then record whether the blocker is cleared or if any unrelated failures remain.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Re-ran `bun run typecheck` against the current workspace and the prior `src/anki-integration/anki-connect-proxy.test.ts` blocker no longer reproduces.
Focused verification passed for `bun test src/anki-integration/anki-connect-proxy.test.ts`. Core verifier now passes `typecheck` and reaches `test:fast`.
Current remaining unrelated verifier failure is unchanged local environment behavior in `scripts/update-aur-package.test.ts`: `scripts/update-aur-package.sh: line 71: mapfile: command not found` under macOS Bash. Artifact: `.tmp/skill-verification/subminer-verify-20260319-200320-vy2YHa`.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Verified the previously reported PR #19 follow-up typecheck blocker is cleared in the current workspace. `bun run typecheck` now passes, and the focused proxy regression file `src/anki-integration/anki-connect-proxy.test.ts` also passes, including the background-enrichment response timing test.
Re-running the SubMiner core verifier confirms the blocker moved forward: `core/typecheck` passes, and the remaining `core/test-fast` failure is unrelated to the proxy test. The only red is the existing macOS Bash compatibility issue in `scripts/update-aur-package.test.ts`, where `scripts/update-aur-package.sh` uses `mapfile` and exits with `line 71: mapfile: command not found`.
Verification run:
- `bun run typecheck`
- `bun test src/anki-integration/anki-connect-proxy.test.ts`
- `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane core src/anki-integration/anki-connect-proxy.test.ts`
Verifier result:
- `core/typecheck` passed
- `core/test-fast` failed only in `scripts/update-aur-package.test.ts` because local macOS Bash lacks `mapfile`
- Artifact: `.tmp/skill-verification/subminer-verify-20260319-200320-vy2YHa`
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,73 +0,0 @@
---
id: TASK-208
title: 'Assess newest PR #19 CodeRabbit round after 1227706'
status: Done
assignee:
- '@codex'
created_date: '2026-03-20 03:37'
updated_date: '2026-03-23 03:22'
labels:
- pr-review
- launcher
- anki-integration
milestone: m-1
dependencies: []
references:
- launcher/commands/stats-command.ts
- launcher/mpv.ts
- src/anki-integration.ts
priority: medium
ordinal: 122500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Validate the newest 2026-03-20 03:23 CodeRabbit review round on PR #19 after commit `1227706`, implement only the confirmed fixes, and record any bot suggestions that are stale or technically incomplete.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Each newest-round CodeRabbit inline comment posted after commit `1227706` is validated against current branch behavior and classified as actionable or not warranted
- [x] #2 Confirmed issues are fixed with focused regression coverage where practical
- [x] #3 Targeted verification runs for the touched areas succeed or remaining unrelated failures are documented
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Pull the three newest CodeRabbit inline threads posted after commit `1227706` and restate each finding against the current branch code.
2. For each confirmed behavior bug, add or extend a focused failing test before changing production code; reject any stale or incorrect bot suggestion with notes.
3. Patch the smallest safe fixes in `launcher/commands/stats-command.ts`, `launcher/mpv.ts`, and/or `src/anki-integration.ts` as warranted, without disturbing unrelated local edits.
4. Run targeted tests and the cheapest sufficient verifier lanes, then record accepted versus rejected comments in task notes and summary.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Validated the newest 2026-03-20 03:23 CodeRabbit round as three comments: two actionable launcher issues and one non-warranted Anki suggestion.
Accepted fixes: cancel the pending stats response poll when the attached app exits non-zero before startup response, and surface `spawnSync()` launch/stop errors in launcher mpv helpers instead of treating `result.status ?? 0` / ignored status as success.
Rejected fix: the `src/anki-integration.ts` / card-creation suggestion would double count locally mined cards. Local sentence mining already records stats in `src/main/runtime/anki-actions.ts` when `mineSentenceCardCore` returns `true`; adding a second callback in card creation would increment tracker counts twice for the same card.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Assessed the newest CodeRabbit PR #19 round after commit `1227706` and fixed the two confirmed launcher regressions. `runStatsCommand()` now gives the startup response waiter an abort signal and cancels the polling loop immediately when the attached app exits non-zero before startup response, covering both the normal stats startup race and the cleanup/startup race. `launchTexthookerOnly()` now fails non-zero when `spawnSync()` reports an execution error, and `stopOverlay()` logs a warning when the stop command cannot be spawned or exits non-zero instead of silently treating that path as success.
One bot comment was intentionally rejected: recording mined-card stats inside the direct card-creation path would double count locally mined cards, because the successful local mining flow already records cards in `src/main/runtime/anki-actions.ts` after `mineSentenceCardCore()` returns `true`.
Verification run:
- `bun test launcher/commands/command-modules.test.ts`
- `bun test launcher/mpv.test.ts`
- `bun run typecheck`
- `bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh launcher/commands/stats-command.ts launcher/commands/command-modules.test.ts launcher/mpv.ts launcher/mpv.test.ts`
- `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane launcher-plugin launcher/commands/stats-command.ts launcher/commands/command-modules.test.ts launcher/mpv.ts launcher/mpv.test.ts`
Verifier result:
- `launcher-plugin` lane passed (`test:launcher:smoke:src`, `test:plugin:src`)
- `typecheck` passed
- Verifier artifacts: `.tmp/skill-verification/subminer-verify-20260319-204639-dzUj16`
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,60 +0,0 @@
---
id: TASK-209
title: Exclude grammar-tail そうだ from subtitle annotations
status: Done
assignee:
- codex
created_date: '2026-03-20 04:06'
updated_date: '2026-03-23 03:22'
labels:
- bug
- tokenizer
dependencies: []
references:
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/tokenizer/annotation-stage.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/tokenizer/annotation-stage.test.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/tokenizer.test.ts
priority: high
ordinal: 120500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Sentence-final grammar-tail `そうだ` tokens can still receive subtitle annotation styling, including frequency highlighting, when Yomitan returns a standalone `そうだ` token and MeCab enriches it as an auxiliary-stem/coupla pattern (`名詞|助動詞`, `助動詞語幹`). Keep the subtitle text visible, but treat this grammar tail like other grammar-only endings so it renders without annotation metadata.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Sentence-final grammar-tail `そうだ` tokens enriched as auxiliary-stem/copula patterns do not receive frequency highlighting or other subtitle annotation metadata.
- [x] #2 The preceding lexical token in cases like `与えるそうだ` keeps its existing annotation behavior.
- [x] #3 Regression tests cover the annotation-stage exclusion and end-to-end subtitle tokenization for the `そうだ` grammar-tail case.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add focused regression coverage for the reported `与えるそうだ` case at both annotation-stage and tokenizeSubtitle levels.
2. Reproduce failure by modeling the MeCab-enriched grammar-tail shape (`名詞|助動詞`, `特殊`, `助動詞語幹`) that currently keeps frequency metadata.
3. Update subtitle-annotation exclusion logic to recognize auxiliary-stem/copula grammar tails via POS metadata plus normalized tail text, not a raw sentence-specific string match.
4. Re-run targeted tokenizer and annotation-stage tests, then record the verification commands and outcome in the task notes.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Investigated reported `与えるそうだ` case. MeCab tags `そう` as `名詞,特殊,助動詞語幹` and `だ` as `助動詞`; after overlap enrichment the Yomitan token becomes `pos1=名詞|助動詞`, `pos2=特殊`, `pos3=助動詞語幹`, which currently escapes subtitle-annotation exclusion and can keep a frequency rank.
Implemented a POS-shape subtitle-annotation exclusion for MeCab-enriched auxiliary-stem grammar tails. The new predicate keys off merged tokens whose POS tags stay within `名詞/助動詞/助詞` and whose POS3 includes `助動詞語幹`, which clears annotation metadata for `そうだ`-style tails without hard-coding the full subtitle text.
Verification: `bun test src/core/services/tokenizer/annotation-stage.test.ts`, `bun test src/core/services/tokenizer.test.ts --test-name-pattern 'explanatory ending|interjection|single-kana merged tokens from frequency highlighting|auxiliary-stem そうだ grammar tails|composite function/content token from frequency highlighting|keeps frequency for content-led merged token with trailing colloquial suffixes'`
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Added regression coverage for `与えるそうだ` and updated subtitle annotation exclusion logic to drop annotation metadata for MeCab-enriched auxiliary-stem grammar tails. The fix is POS-driven rather than sentence-specific, so `そうだ`-style grammar endings stay visible/hoverable as plain text while neighboring lexical tokens keep their existing frequency/JLPT behavior.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,63 +0,0 @@
---
id: TASK-210
title: Show latest session position in anime episode progress
status: Done
assignee:
- '@Codex'
created_date: '2026-03-20 04:09'
updated_date: '2026-03-23 03:22'
labels:
- stats
- bug
- ui
milestone: m-1
dependencies: []
references:
- stats/src/components/anime/EpisodeList.tsx
- src/core/services/immersion-tracker/query.ts
- src/core/services/immersion-tracker/session.ts
- src/core/services/immersion-tracker-service.ts
ordinal: 121500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Anime episode rows in stats can show watch time and lookups from the latest session while the Progress column stays blank because it only reads `ended_media_ms` from ended sessions. Update the progress source so a just-watched episode reflects the latest known session stop position without falling back to cumulative watch time.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Anime episode progress uses the latest known session position for the episode, including the most recent active session when available.
- [x] #2 Ended-session progress remains correct and does not regress to cumulative watch time.
- [x] #3 Regression coverage locks query and/or UI behavior for active-session and ended-session episode progress.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add failing regression coverage for anime episode progress when the latest session is still active but has a known playback position.
2. Persist the latest playback position on the active `imm_sessions` row during playback so stats queries can read it before session finalization.
3. Update anime episode queries to use the newest known session position for progress while preserving ended-session behavior.
4. Run targeted verification for immersion tracker, stats query, and cheap repo checks; record results and task outcome.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Root cause: stale active-session recovery rebuilt session state with `lastMediaMs = null`, so `finalizeSessionRecord` overwrote persisted progress checkpoints with `ended_media_ms = NULL` during startup reconciliation.
Implemented telemetry-flush checkpointing to persist `lastMediaMs` onto the active `imm_sessions` row, preserved that checkpoint through stale-session reconciliation, and updated anime episode progress queries to read the latest known non-null session position across active or ended sessions.
Verification: targeted regressions passed (`bun test src/core/services/immersion-tracker-service.test.ts --test-name-pattern 'flushTelemetry checkpoints latest playback position on the active session row|startup finalizes stale active sessions and applies lifetime summaries'`, `bun test src/core/services/immersion-tracker/__tests__/query.test.ts --test-name-pattern 'getAnimeEpisodes prefers the latest session media position when the latest session is still active|getAnimeEpisodes returns latest ended media position and aggregate metrics'`), broader tracker/query suite passed (`bun test src/core/services/immersion-tracker-service.test.ts src/core/services/immersion-tracker/__tests__/query.test.ts`), `bun run typecheck` passed via verifier, `bun run changelog:lint` passed.
Verification blocker: `.agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane core ...` reported `bun run test:fast` failure from pre-existing `scripts/update-aur-package.test.ts` (`mapfile: command not found` under bash), unrelated to this change set.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Persist anime episode progress checkpoints before session finalization so stats can survive crashes/restarts and still show the latest known watch position. Telemetry flushes now checkpoint `lastMediaMs` onto the active `imm_sessions` row, stale-session recovery preserves that checkpoint when finalizing recovered sessions, and `getAnimeEpisodes` now reads the newest non-null session position whether it came from an active or ended session.
Added regressions for active-session checkpoint persistence, stale-session recovery preserving `ended_media_ms`, and episode queries preferring the latest known session position. Verification passed for the targeted and broader immersion tracker/query suites, plus `bun run typecheck` and `bun run changelog:lint`. The verifier's `bun run test:fast` step still fails on the pre-existing `scripts/update-aur-package.test.ts` bash `mapfile` issue, which is outside this task's scope.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,41 +0,0 @@
---
id: TASK-211
title: >-
Recover anime episode progress from subtitle timing when checkpoints are
missing
status: Done
assignee:
- '@Codex'
created_date: '2026-03-20 10:15'
updated_date: '2026-03-23 03:22'
labels:
- stats
- bug
milestone: m-1
dependencies: []
references:
- src/core/services/immersion-tracker/query.ts
- src/core/services/immersion-tracker/__tests__/query.test.ts
ordinal: 119500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Anime episode progress can still show `0%` for older sessions that have watch-time and subtitle timing but no persisted `ended_media_ms` checkpoint. Recover progress from the latest retained subtitle/event segment end so already-recorded sessions render a useful progress percentage.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 `getAnimeEpisodes` returns the latest known session position even when `ended_media_ms` is null but subtitle/event timing exists.
- [x] #2 Existing ended-session metrics and aggregation totals do not regress.
- [x] #3 Regression coverage locks the fallback behavior.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Added a query-side fallback for anime episode progress: when the newest session for a video has no persisted `ended_media_ms`, `getAnimeEpisodes` now uses the latest retained subtitle-line or session-event `segment_end_ms` from that same session. This recovers useful progress for already-recorded sessions that have timing data but predate or missed checkpoint persistence.
Verification: `bun test src/core/services/immersion-tracker/__tests__/query.test.ts` passed. `bun run typecheck` passed.
<!-- SECTION:NOTES:END -->

View File

@@ -1,44 +0,0 @@
---
id: TASK-212
title: Fix mac texthooker helper startup blocking mpv launch
status: Done
assignee: []
created_date: '2026-03-20 08:27'
updated_date: '2026-03-23 03:22'
labels:
- bug
- macos
- startup
dependencies: []
references:
- /Users/sudacode/projects/japanese/SubMiner/src/core/services/startup.ts
- /Users/sudacode/projects/japanese/SubMiner/src/main.ts
- /Users/sudacode/projects/japanese/SubMiner/plugin/subminer/process.lua
priority: high
ordinal: 140500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
`subminer` mpv auto-start on mac can stall before the video is usable because the helper process launched with `--texthooker` still runs heavy app-ready startup. Recent logs show the helper loading the Yomitan Chromium extension, emitting `Permission 'contextMenus' is unknown` warnings, then hitting Chromium runtime errors before SubMiner signals readiness back to the mpv plugin. The texthooker helper should take the minimal startup path needed to serve texthooker traffic without loading overlay/window-only startup work that can crash or delay readiness.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Launching SubMiner with `--texthooker` avoids heavy app-ready startup work that is not required for texthooker helper mode.
- [x] #2 A regression test covers texthooker helper startup so it fails if Yomitan extension loading is reintroduced on that path.
- [x] #3 The change preserves existing startup behavior for non-texthooker app launches.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Follow-up: user confirmed the root issue is the plugin auto-start ordering. Adjust mpv plugin sequencing so `--start` launches before any separate `--texthooker` helper, then verify plugin regressions still pass.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed the mac mpv startup hang caused by the `--texthooker` helper taking the full app-ready path. `runAppReadyRuntime` now fast-paths texthooker-only mode through minimal startup (`reloadConfig` plus CLI handling) so it no longer loads Yomitan or first-run setup work before serving texthooker traffic. Added regression coverage in `src/core/services/app-ready.test.ts`, then verified with `bun test src/core/services/app-ready.test.ts src/core/services/startup.test.ts`, `bun test src/cli/args.test.ts src/main/early-single-instance.test.ts src/main/runtime/stats-cli-command.test.ts`, and `bun run typecheck`.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,43 +0,0 @@
---
id: TASK-213
title: Show character dictionary progress during paused startup waits
status: Done
assignee: []
created_date: '2026-03-20 08:59'
updated_date: '2026-03-23 03:22'
labels:
- bug
- ux
- dictionary
- startup
dependencies: []
references:
- >-
/Users/sudacode/projects/japanese/SubMiner/src/main/runtime/startup-osd-sequencer.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/main/runtime/character-dictionary-auto-sync-notifications.ts
- /Users/sudacode/projects/japanese/SubMiner/src/main.ts
priority: medium
ordinal: 141500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
During startup on mpv auto-start, character dictionary regeneration/update can be active while playback remains paused. The current startup OSD sequencer buffers dictionary progress behind annotation-loading OSD, which leaves the user with no visible dictionary-specific progress while the pause is active. Adjust the startup OSD sequencing so dictionary progress can surface once tokenization is ready during the paused startup window, without regressing later ready/failure handling.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 When tokenization is ready during startup, later character dictionary progress updates are shown on OSD even if annotation-loading state is still active.
- [ ] #2 Startup OSD completion/failure behavior for character dictionary sync remains coherent after the new progress ordering.
- [ ] #3 Regression coverage exercises the paused startup sequencing for dictionary progress.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
2026-03-20: Confirmed issue is broader than OSD-only. Paused-startup OSD fixes remain relevant, but current user report also points at a regression in non-blocking startup playback release (tracked in TASK-143).
2026-03-20: OSD sequencing fix remains in local patch alongside TASK-143 regression fix. Covered by startup-osd-sequencer tests; pending installed-app/mpv validation before task finalization.
<!-- SECTION:NOTES:END -->

View File

@@ -1,40 +0,0 @@
---
id: TASK-214
title: Jump subtitle sidebar directly to resume position on first resolved cue
status: Done
assignee: []
created_date: '2026-03-21 11:15'
updated_date: '2026-03-23 03:22'
labels:
- bug
- ux
- overlay
- subtitles
dependencies: []
references:
- >-
/Users/sudacode/projects/japanese/SubMiner/src/renderer/modals/subtitle-sidebar.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/renderer/modals/subtitle-sidebar.test.ts
priority: medium
ordinal: 142500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
When playback starts from a resumed timestamp while the subtitle sidebar is open, the sidebar currently smooth-scrolls from the top of the cue list to the resumed cue. Change the first resolved active-cue positioning to jump immediately to the resume location while preserving smooth auto-follow for later playback-driven cue advances.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 The first active cue resolved after open/resume uses an instant jump instead of smooth-scrolling through the list.
- [x] #2 Normal subtitle-sidebar auto-follow remains smooth after the first active cue has been positioned.
- [x] #3 Regression coverage distinguishes the initial jump behavior from later smooth auto-follow updates.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
2026-03-21: Fixed by treating the first auto-scroll from `previousActiveCueIndex < 0` as `behavior: 'auto'` in the subtitle sidebar scroll helper. Added renderer regression coverage for initial jump plus later smooth follow.
<!-- SECTION:NOTES:END -->

View File

@@ -1,44 +0,0 @@
---
id: TASK-215
title: Add startup auto-open option for subtitle sidebar
status: Done
assignee: []
created_date: '2026-03-21 11:35'
updated_date: '2026-03-23 03:22'
labels:
- feature
- ux
- overlay
- subtitles
dependencies: []
references:
- /Users/sudacode/projects/japanese/SubMiner/src/types.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/config/definitions/defaults-subtitle.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/config/resolve/subtitle-domains.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/renderer/modals/subtitle-sidebar.ts
- /Users/sudacode/projects/japanese/SubMiner/src/renderer/renderer.ts
priority: medium
ordinal: 143500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add a subtitle sidebar config option that auto-opens the sidebar once during overlay startup. The option should default to `false`, only apply when the sidebar feature is enabled, and should not force the sidebar back open later in the same session after manual close or later visibility changes.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 `subtitleSidebar.autoOpen` is available in config with default `false`.
- [x] #2 When enabled, overlay startup opens the subtitle sidebar once after initial sidebar config/snapshot load.
- [x] #3 Regression coverage covers config resolution and startup-only auto-open behavior.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
2026-03-21: Added `subtitleSidebar.autoOpen` to types/defaults/config registry and resolver. Renderer bootstrap now calls a startup-only subtitle sidebar helper after the initial snapshot refresh. Modal regression coverage verifies startup auto-open requires both `enabled` and `autoOpen`.
<!-- SECTION:NOTES:END -->

View File

@@ -1,70 +0,0 @@
---
id: TASK-217
title: Fix embedded overlay passthrough sync between subtitle and sidebar
status: Done
assignee:
- codex
created_date: '2026-03-21 23:16'
updated_date: '2026-03-23 03:22'
labels:
- bug
- overlay
- macos
dependencies: []
references:
- src/renderer/handlers/mouse.ts
- src/renderer/modals/subtitle-sidebar.ts
- src/renderer/renderer.ts
documentation:
- docs/workflow/verification.md
priority: high
ordinal: 118500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
On macOS, when both the subtitle overlay and embedded subtitle sidebar are visible, mouse passthrough to mpv can remain stale until the user hovers the sidebar. After closing the sidebar, passthrough can likewise remain stale until the user hovers the subtitle again. Fix the overlay input-state synchronization so passthrough reflects the current hover/open state immediately instead of relying on the last hover target.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 When the embedded subtitle sidebar is open and the pointer is not over subtitle or sidebar content, the overlay returns to mouse passthrough immediately without requiring a sidebar hover cycle.
- [x] #2 When transitioning between subtitle hover and sidebar hover states on macOS embedded sidebar mode, mouse ignore state stays in sync with the currently interactive region.
- [x] #3 Closing the embedded subtitle sidebar restores the correct passthrough state based on remaining subtitle hover/modal state without requiring an additional hover.
- [x] #4 Regression tests cover the passthrough synchronization behavior.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a shared renderer-side passthrough sync helper that derives whether the overlay should ignore mouse events from subtitle hover, embedded sidebar visibility/hover, popup visibility, and modal state.
2. Replace direct embedded-sidebar passthrough toggles in subtitle hover/sidebar handlers with calls to the shared sync helper so state is recomputed on every transition.
3. Add regression tests for macOS embedded sidebar mode covering sidebar-open idle passthrough, subtitle-to-sidebar transitions, and sidebar-close restore behavior.
4. Run targeted renderer tests for mouse/sidebar passthrough coverage, then summarize any residual risk.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Added shared renderer overlay mouse-ignore recompute so subtitle hover, embedded sidebar hover/open/close, and popup idle transitions all derive passthrough from current state instead of last hover target.
Added regression coverage for embedded sidebar idle passthrough on subtitle leave and for sidebar-close recompute behavior.
Verification: `bun run typecheck` passed; `bun test src/renderer/handlers/mouse.test.ts` passed; `bun test src/renderer/modals/subtitle-sidebar.test.ts` passed; core verification wrapper artifact at `.tmp/skill-verification/subminer-verify-20260321-162743-XhSBxw` hit an unrelated `bun run test:fast` failure in `scripts/update-aur-package.test.ts` because macOS system bash lacks `mapfile`.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed stale embedded-sidebar passthrough sync on macOS by introducing a shared renderer mouse-ignore recompute path and tracking sidebar-hover state separately from subtitle hover. Subtitle hover leave, sidebar hover enter/leave, sidebar open, and sidebar close now all recompute passthrough from the current overlay state instead of waiting for a later hover event to repair it. Added regression tests covering subtitle-leave passthrough while the embedded sidebar is open but idle, plus sidebar-close restore behavior based on remaining subtitle hover state.
Tests run:
- `bun run typecheck`
- `bun test src/renderer/handlers/mouse.test.ts`
- `bun test src/renderer/modals/subtitle-sidebar.test.ts`
- `bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh src/renderer/state.ts src/renderer/overlay-mouse-ignore.ts src/renderer/handlers/mouse.ts src/renderer/handlers/mouse.test.ts src/renderer/modals/subtitle-sidebar.ts src/renderer/modals/subtitle-sidebar.test.ts`
- `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane core src/renderer/state.ts src/renderer/overlay-mouse-ignore.ts src/renderer/handlers/mouse.ts src/renderer/handlers/mouse.test.ts src/renderer/modals/subtitle-sidebar.ts src/renderer/modals/subtitle-sidebar.test.ts` (typecheck passed; `test:fast` blocked by unrelated `scripts/update-aur-package.test.ts` failure on macOS Bash 3.2 lacking `mapfile`)
Risk: the classifier flagged this as a real-runtime candidate, so actual Electron/mpv macOS pointer behavior was not exercised in a live runtime during this turn.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,69 +0,0 @@
---
id: TASK-218
title: Delete zero-session media from stats library and trends
status: Done
assignee:
- codex
created_date: '2026-03-22 16:20'
updated_date: '2026-03-24 06:41'
labels:
- stats
- immersion-tracker
dependencies: []
references:
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/immersion-tracker/query.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/immersion-tracker/lifetime.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/immersion-tracker/maintenance.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/immersion-tracker/__tests__/query.test.ts
priority: medium
ordinal: 153500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Deleting the last retained session for a video still left stale lifetime media rows and trend rollups behind, so the stats dashboard could continue showing ghost entries in Library and Trends after all sessions were gone.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Deleting the final session for a video removes that media from Library queries and detail reads
- [x] #2 Deleting the final session for a video removes stale daily/monthly trend rollups for that media
- [x] #3 Regression coverage proves zero-session media disappears from affected stats surfaces after deletion
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a failing regression around deleting the only retained session for a video while preexisting lifetime and rollup rows exist.
2. Patch the deletion path to rebuild lifetime and rollup state from retained sessions inside the same transaction.
3. Run focused immersion-tracker tests plus the repo-native verifier core lane and record results.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Added a query regression that seeds a finished session plus stale lifetime media/anime rows and daily/monthly rollups, deletes that only session, and asserts Library, Anime detail, and Trends all drop the media immediately.
Refactored lifetime rebuild logic so it can run inside an existing delete transaction, then reused that helper from `deleteSession`, `deleteSessions`, and `deleteVideo`.
Added a rollup rebuild helper that clears existing daily/monthly rollups and reconstructs them from retained telemetry inside the current transaction so deleted sessions cannot leave ghost trend points behind.
Verification passed:
- `bun test src/core/services/immersion-tracker/__tests__/query.test.ts`
- `bun test src/core/services/immersion-tracker-service.test.ts`
- `bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh src/core/services/immersion-tracker/query.ts src/core/services/immersion-tracker/lifetime.ts src/core/services/immersion-tracker/maintenance.ts src/core/services/immersion-tracker/__tests__/query.test.ts`
- `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane core src/core/services/immersion-tracker/query.ts src/core/services/immersion-tracker/lifetime.ts src/core/services/immersion-tracker/maintenance.ts src/core/services/immersion-tracker/__tests__/query.test.ts`
Verifier artifact dir: `.tmp/skill-verification/subminer-verify-20260322-210718-n6sGL8`
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Delete paths now rebuild lifetime summaries and trend rollups after removing sessions, so when the last session for a video disappears the stats database also drops that media from Library, related detail reads, and chart data. Added a regression proving a video with only stale lifetime/rollup rows vanishes after its final session is deleted, and verified the change with focused immersion-tracker tests plus the SubMiner core verification lane.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,66 +0,0 @@
---
id: TASK-220
title: Restore YouTube overlay mpv keybindings after picker routing
status: Done
assignee:
- codex
created_date: '2026-03-22 00:00'
updated_date: '2026-03-22 23:49'
labels:
- bug
- overlay
- youtube
- keyboard
dependencies: []
references:
- src/renderer/handlers/keyboard.ts
- src/renderer/modals/youtube-track-picker.ts
- src/renderer/handlers/keyboard.test.ts
- src/renderer/modals/youtube-track-picker.test.ts
documentation:
- docs/workflow/verification.md
priority: high
ordinal: 118800
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Regression: after adding the YouTube subtitle picker modal path, visible-overlay keydown handling can stop before reaching the shared mpv keybinding dispatch path. Result: default overlay mpv bindings like `Space` pause/play and `q` quit stop working while the overlay owns focus during YouTube playback.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Unhandled keys while the YouTube track picker state is active still fall through to the shared overlay mpv keybinding dispatcher.
- [x] #2 The YouTube picker continues to consume `Enter` and `Escape` for its own actions.
- [x] #3 Renderer regression tests cover both the picker modal key contract and the shared keyboard dispatch fallback.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a failing renderer keyboard regression test covering YouTube picker state plus shared mpv keybinding fallback.
2. Update the global keyboard handler to return early only when the YouTube picker actually handles the key event.
3. Update the picker modal handler to return false for unhandled keys while preserving `Enter`/`Escape`.
4. Run the cheap renderer verification lane and record results.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Fixed the regression by making the global renderer keyboard handler stop early for the YouTube picker only when the picker actually consumes the key. The picker modal now returns `false` for unrelated keys, so shared overlay mpv bindings like `Space` and `KeyQ` still dispatch while the visible overlay has focus.
Added regression coverage in the keyboard handler suite for mpv keybinding fallback during YouTube picker state, plus a picker-modal contract test that keeps `Escape` handled but leaves unrelated keys unclaimed.
Verification:
- `bun test src/renderer/handlers/keyboard.test.ts src/renderer/modals/youtube-track-picker.test.ts`
- `bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh src/renderer/handlers/keyboard.ts src/renderer/handlers/keyboard.test.ts src/renderer/modals/youtube-track-picker.ts src/renderer/modals/youtube-track-picker.test.ts`
- `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane core src/renderer/handlers/keyboard.ts src/renderer/handlers/keyboard.test.ts src/renderer/modals/youtube-track-picker.ts src/renderer/modals/youtube-track-picker.test.ts`
- verifier artifact: `.tmp/skill-verification/subminer-verify-20260322-234831-b2m6nJ`
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Restored YouTube-session overlay mpv keybindings by removing an unconditional early return added to the renderer keyboard path for the YouTube subtitle picker modal. Unhandled keys now fall through to the shared mpv keybinding dispatcher, while handled picker keys (`Enter`, `Escape`) still stay local to the picker. Added renderer regression tests for both the keyboard fallback path and the picker modal key-consumption contract.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,58 +0,0 @@
---
id: TASK-221
title: 'Assess and address PR #31 latest CodeRabbit review'
status: Done
assignee: []
created_date: '2026-03-23 07:53'
updated_date: '2026-03-24 06:41'
labels:
- pr-review
- coderabbit
dependencies: []
references:
- >-
PR #31 feat: add app-owned YouTube subtitle flow with absPlayer-style
parsing
priority: medium
ordinal: 152500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Inspect the latest CodeRabbit review on PR #31, evaluate each actionable comment against the current branch, implement valid fixes, verify the changes, and prepare PR thread updates.
<!-- SECTION:DESCRIPTION:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Inspect latest CodeRabbit review on PR #31 and separate valid action items from non-blocking suggestions.
2. Add regression coverage for any real bugs before changing production code.
3. Implement the minimal fixes for confirmed issues in runtime, renderer modal flow, and test fixtures.
4. Run targeted tests plus repo-native verification lanes.
5. Update PR threads with fix status and rationale for any comments not actioned yet.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Implemented and pushed commit 207151db to PR #31.
Replied in-thread to the CodeRabbit comments for YouTube host matching, duplicate picker submissions, and missing MediaDetailView test fixture videoId fields.
Follow-up scope added: update release-facing docs/changelog for the YouTube subtitle picker work and run a release-readiness gate before handoff.
Added release-facing docs/changelog updates in commit b7e0026d and pushed them to PR #31.
Ran the release-readiness gate: changelog:lint, changelog:pr-check, verify:config-example, typecheck, test:fast, test:env, build, test:smoke:dist, docs:test, docs:build.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Assessed the latest CodeRabbit review on PR #31 and applied the confirmed fixes. Tightened `isYoutubeMediaPath()` to match only exact YouTube hosts or subdomains with a regression test for `notyoutube.com`, added an in-flight guard plus temporary control disabling to the YouTube track picker with a duplicate-submit regression test, replaced the picker empty-state `innerHTML` fallback with explicit DOM construction, and added the missing `videoId` fields to the `MediaDetailView` test fixtures. Verified with targeted Bun tests and the `runtime-compat` verification lane (`build`, `test:runtime:compat`, `test:smoke:dist`).
Updated `README.md`, `docs-site/usage.md`, and `changes/2026-03-23-immersion-youtube.md` so the PR is release-facing and user-visible surfaces describe the YouTube subtitle picker flow plus its latest hardening.
Release-readiness checks passed locally: `bun run changelog:lint`, `bun run changelog:pr-check`, `bun run verify:config-example`, `bun run typecheck`, `bun run test:fast`, `bun run test:env`, `bun run build`, `bun run test:smoke:dist`, `bun run docs:test`, and `bun run docs:build`.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,56 +0,0 @@
---
id: TASK-222
title: Fix YouTube overlay keybindings in subtitle path
status: Done
assignee:
- codex
created_date: '2026-03-23 08:32'
updated_date: '2026-03-24 06:41'
labels:
- bug
dependencies: []
references:
- /Users/sudacode/projects/japanese/SubMiner/src/main/runtime
- /Users/sudacode/projects/japanese/SubMiner/src/core/services
priority: high
ordinal: 151500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Users watching video through the YouTube subtitle path cannot use some overlay keyboard controls such as quit and pause/play. Restore expected overlay keybinding behavior for that playback path without regressing other overlay input handling.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Overlay quit and pause/play keybindings work while using the YouTube subtitle path.
- [x] #2 Existing overlay keybinding behavior for non-YouTube playback remains unchanged.
- [x] #3 Regression coverage exercises the YouTube subtitle path keyboard handling.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a regression test around YouTube track-picker close to verify it requests main-process main-window focus restoration before returning overlay focus locally.
2. Update the YouTube track-picker close flow to call `window.electronAPI.focusMainWindow()` alongside the existing `window.focus()` and `overlay.focus()` restoration.
3. Run targeted tests for the picker/keyboard paths to verify YouTube playback regains overlay keybindings without regressing existing overlay behavior.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Investigated overlay input path. Renderer already maps Space/KeyQ to mpv commands, but YouTube track-picker close only restores DOM focus (`window.focus` + `overlay.focus`) and does not invoke main-process window focus recovery, unlike the keyboard-mode focus reclaim path. Suspected root cause: overlay BrowserWindow focus is not restored after the YouTube picker closes, so playback keybindings stop reaching renderer keydown handlers.
User approved implementation plan on 2026-03-23. Proceeding with TDD: add failing regression first, then minimal fix, then targeted verification.
Implemented fix in the YouTube track-picker close path: request main-process `focusMainWindow()` before restoring renderer window/overlay focus so overlay keydown handlers regain input after YouTube subtitle selection.
Verification: `bun test src/renderer/modals/youtube-track-picker.test.ts` and `bun test src/renderer/handlers/keyboard.test.ts` both pass.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Restored overlay keyboard focus after closing the YouTube subtitle picker by invoking the main-process `focusMainWindow()` recovery path before local window/overlay focus restoration. Added regression coverage to the YouTube picker modal test and verified existing keyboard handler coverage for YouTube picker passthrough keys (`Space`, `KeyQ`) remains green.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,109 +0,0 @@
---
id: TASK-223
title: Fix YouTube overlay Anki initialization regression
status: Done
assignee:
- codex
created_date: '2026-03-23 08:41'
updated_date: '2026-03-24 06:41'
labels:
- bug
- youtube
- anki
dependencies: []
references:
- /Users/sudacode/projects/japanese/SubMiner/src/main.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/overlay-runtime-init.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/main/runtime/cli-command-runtime-handler.ts
documentation:
- /Users/sudacode/projects/japanese/SubMiner/docs/workflow/verification.md
priority: high
ordinal: 154500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Restore Anki-backed lookup and known-word behavior during YouTube playback. Recent startup changes appear to let the YouTube flow initialize the overlay before runtime prerequisites exist, leaving the Anki integration unavailable for popup Mine actions and known-word highlighting.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 YouTube playback initializes Anki integration once overlay startup prerequisites are available so lookup can offer card-add actions again
- [x] #2 Known-word / N+1 state is available during YouTube playback when the user has Anki-backed known-word highlighting enabled
- [x] #3 Regression coverage fails before the fix and passes after it for the YouTube startup path
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a regression test covering the YouTube playback command path and assert overlay startup prerequisites are established before the flow runs.
2. Reuse the overlay startup prerequisite bootstrap for the YouTube playback path so Anki integration sees subtitle tracker, mpv client, and runtime options manager before initialization.
3. Verify with focused runtime/CLI tests, then run the cheapest sufficient verification lane for the touched files.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Identified regression path in CLI command runtime: YouTube playback commands could reach overlay initialization without first materializing overlay startup prerequisites, leaving Anki integration unavailable during the initial startup attempt.
Added a regression test at src/main/runtime/cli-command-runtime-handler.test.ts covering youtubePlay command dispatch outside texthooker-only mode.
Verified with bun test src/main/runtime/cli-command-runtime-handler.test.ts src/main/runtime/cli-command-prechecks.test.ts src/main/runtime/cli-command-prechecks-main-deps.test.ts and bun test src/core/services/cli-command.test.ts src/main/runtime/cli-command-context-main-deps.test.ts src/main/runtime/cli-command-prechecks-main-deps.test.ts src/main/runtime/cli-command-runtime-handler.test.ts.
Removed the YouTube-only ensureOverlayRuntimeReady path from src/main.ts after confirming regular app startup already loads Yomitan and the shared CLI overlay pre-dispatch bootstrap now covers overlay prerequisites.
Moved overlay bootstrap into the generic initial-args startup path for any initial command that needs overlay runtime, so overlay prerequisites and overlay initialization happen before CLI dispatch instead of inside a YouTube-only or last-moment command path.
Additional verification passed: bun test src/main/runtime/initial-args-runtime-handler.test.ts src/main/runtime/initial-args-handler.test.ts src/main/runtime/initial-args-main-deps.test.ts, bun run typecheck, bun run test:runtime:compat
Follow-up regression: subtitle picker was still auto-submitting the default selection during YouTube startup. Investigating renderer-side immediate Enter key bleed-through on picker open.
Root cause for remaining picker regression: the YouTube track picker accepted Enter immediately on open, so the launch keypress could auto-submit the default track selection before the modal was visible to the user.
Added renderer regression coverage in src/renderer/modals/youtube-track-picker.test.ts proving immediate Enter after open is ignored and a later Enter still submits normally.
Implemented a 200ms open-key guard in src/renderer/modals/youtube-track-picker.ts for Enter-based submission only; Escape/click behavior unchanged.
New follow-up regression report: YouTube subtitle picker can open before the mpv playback window is ready, leaving the picker behind the overlay after geometry snaps into place. Investigating picker-open gating and modal-targeting timing.
Identified likely cause of picker-behind-overlay regression: YouTube picker open logic mixed overlay targets. First attempt preferred the visible main overlay, timeout retry switched to the dedicated modal window, allowing a late first open to cover the modal.
Extracted picker-open policy into src/main/runtime/youtube-picker-open.ts and changed YouTube picker startup to always target the dedicated modal window, including retries. This keeps the picker on a single window path and lets overlay-runtime hide/click-through the main overlay while the modal is active.
Added regression tests in src/main/runtime/youtube-picker-open.test.ts covering dedicated modal first-open, dedicated-modal retry, and failure when no modal target is available.
User reports overlay flow still feels wrong: YouTube path appears to preload subtitles before mandatory selection and may open the picker before mpv window readiness. Re-evaluating flow design against regular video startup before further implementation.
New follow-up regression report: duplicate overlay windows appear during YouTube playback and only one window shows subtitles. Investigating main-overlay versus dedicated modal-window handoff/cleanup.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed the YouTube Anki initialization regression by making CLI commands that require overlay runtime bootstrap overlay startup prerequisites before command dispatch when not in texthooker-only mode. This ensures the YouTube playback flow has the mpv client, runtime options manager, and subtitle timing tracker ready before overlay/Anki initialization runs, restoring Mine actions and known-word-backed behavior.
Added a regression test covering youtubePlay command dispatch in src/main/runtime/cli-command-runtime-handler.test.ts.
Verification:
- bun test src/main/runtime/cli-command-runtime-handler.test.ts src/main/runtime/cli-command-prechecks.test.ts src/main/runtime/cli-command-prechecks-main-deps.test.ts
- bun test src/core/services/cli-command.test.ts src/main/runtime/cli-command-context-main-deps.test.ts src/main/runtime/cli-command-prechecks-main-deps.test.ts src/main/runtime/cli-command-runtime-handler.test.ts
Updated the fix to avoid a YouTube-specific startup path: removed the dedicated ensureOverlayRuntimeReady helper from src/main.ts and relied on the shared CLI overlay prerequisite bootstrap instead.
Additional verification: bun run typecheck
Follow-up adjustment: initial overlay-runtime commands now bootstrap overlay prerequisites and initialize overlay during the shared initial-args startup path, rather than waiting for command dispatch. This keeps YouTube on the regular startup path while preserving earlier overlay availability.
Additional verification: bun test src/main/runtime/initial-args-runtime-handler.test.ts src/main/runtime/initial-args-handler.test.ts src/main/runtime/initial-args-main-deps.test.ts; bun run test:runtime:compat
Follow-up fix: the YouTube subtitle picker now ignores immediate Enter key bleed-through right after opening, preventing the startup keypress from auto-submitting the default track selection before the modal is visible.
Added renderer regression coverage for immediate Enter suppression and verified with bun test src/renderer/modals/youtube-track-picker.test.ts plus the runtime-compat verification lane for the touched files.
Follow-up fix: YouTube subtitle picker startup now uses a dedicated modal-window path consistently instead of mixing main-overlay first-open with modal-window retry. That prevents late overlay opens from covering the interactive picker while mpv/window tracking settles.
Verified with bun test src/main/runtime/youtube-picker-open.test.ts, bun test src/renderer/modals/youtube-track-picker.test.ts, and the runtime-compat verification lane for src/main.ts plus the touched picker files.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,61 +0,0 @@
---
id: TASK-224
title: >-
Auto-load default YouTube subtitles at playback start and make picker
manual-only
status: Done
assignee:
- Codex
created_date: '2026-03-23 18:51'
updated_date: '2026-03-24 06:41'
labels:
- youtube
- mpv
- overlay
- keybindings
dependencies: []
references:
- /Users/sudacode/projects/japanese/SubMiner/src/main/runtime/youtube-flow.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/renderer/modals/youtube-track-picker.ts
- /Users/sudacode/projects/japanese/SubMiner/src/config/definitions/shared.ts
priority: high
ordinal: 150500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Replace the mandatory YouTube subtitle picker startup flow with automatic default-track loading. On YouTube playback start, attempt to load the default primary subtitle and best-effort secondary subtitle without prompting. Gate playback only on primary subtitle load/tokenization readiness. If primary subtitle probing/download/loading fails, resume playback and report the failure through the configured notification/output path. Keep the YouTube subtitle picker as a regular overlay modal opened by a new default keybinding during active YouTube playback.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Opening a YouTube URL auto-selects and attempts to load the default primary subtitle without opening the picker modal.
- [x] #2 Opening a YouTube URL also attempts to load the default secondary subtitle when available, but playback never waits on secondary success.
- [x] #3 Playback remains gated only until the primary subtitle is loaded and tokenization is ready; primary failure resumes playback immediately.
- [x] #4 Primary auto-load failures report through the existing configured notification/output path and keep playback running.
- [x] #5 The YouTube subtitle picker can be opened manually during active YouTube playback via a new default keybinding.
- [x] #6 Regression tests cover startup auto-load success, primary failure fallback, and the manual picker keybinding flow.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add failing tests for YouTube startup auto-load success, primary failure fallback, and manual picker keybinding flow.
2. Refactor the YouTube runtime to auto-select default tracks on startup, gate playback only on primary subtitle/tokenization readiness, and route failures through the configured notification/output path.
3. Add a new default keybinding and command path to open the YouTube picker manually during active YouTube playback.
4. Run targeted tests, then SubMiner verification lanes for launcher/runtime changes; update docs/changelog if required by the final behavior change.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Verification blocker outside this change: `bun run test:fast` still fails at `scripts/update-aur-package.test.ts` on macOS because `scripts/update-aur-package.sh` uses `mapfile`, which is unavailable in the system Bash 3.x environment used here.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Reworked app-owned YouTube playback to auto-load the default primary subtitle plus a best-effort secondary subtitle at startup instead of forcing the picker modal first. Playback now waits only on primary subtitle load/tokenization readiness, routes startup primary-failure messaging through the configured notification output path, and keeps the YouTube subtitle picker available on demand via a new default `Ctrl+Shift+J` keybinding during active YouTube playback. Updated the runtime/IPC/config plumbing, user-facing help/docs, and added regression coverage for startup auto-load, primary-failure fallback, and manual picker invocation.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,41 +0,0 @@
---
id: TASK-225
title: Fix frozen primary YouTube subtitle display after auto-load startup
status: Done
assignee: []
created_date: '2026-03-23 20:07'
updated_date: '2026-03-24 06:41'
labels:
- bug
- youtube
- subtitles
dependencies:
- TASK-224
priority: high
ordinal: 149500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
After the new YouTube auto-load startup flow, the primary subtitle overlay can stay stuck on an older line while the subtitle sidebar continues advancing. Investigate startup suppression / subtitle refresh timing and restore live primary overlay updates after auto-loaded subtitles are injected.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 When YouTube auto-load succeeds, the visible primary subtitle continues advancing after playback resumes.
- [x] #2 Startup suppression does not leave the primary subtitle display stuck on a stale line.
- [x] #3 A regression test covers the startup path that previously froze the visible primary subtitle while sidebar timing continued advancing.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Root cause: applyStartupState seeded youtubePlaybackFlowPending from initialArgs.youtubePlay, and runYoutubePlaybackFlowMain restored that preexisting true value after startup auto-load. Result: primary subtitle events stayed suppressed for startup-launched YouTube playback while sidebar timing still advanced.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Stopped pre-seeding youtubePlaybackFlowPending from startup CLI args so only the actual YouTube playback bootstrap window suppresses subtitle events. Added a regression test covering startup YouTube args and re-ran targeted YouTube/runtime subtitle tests plus typecheck.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,42 +0,0 @@
---
id: TASK-226
title: Restore subtitle sidebar cues for auto-loaded YouTube subtitles
status: Done
assignee: []
created_date: '2026-03-23 20:21'
updated_date: '2026-03-24 06:41'
labels:
- bug
- youtube
- subtitle-sidebar
dependencies:
- TASK-224
- TASK-225
priority: high
ordinal: 148500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
After fixing startup subtitle event suppression, the primary subtitle overlay updates for auto-loaded YouTube playback but the subtitle sidebar reports no parsed subtitle cues available. Investigate parsed subtitle source registration / refresh for auto-loaded YouTube subtitle files and restore sidebar cue population.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 When YouTube auto-load succeeds, the subtitle sidebar receives parsed cues for the active primary subtitle source.
- [x] #2 Auto-loaded YouTube subtitle source changes refresh the sidebar snapshot without requiring manual picker interaction.
- [x] #3 A regression test covers the startup auto-load path where live primary subtitles render but sidebar cues remain empty.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Root cause: successful YouTube auto-load refreshed visible primary subtitle state, but did not explicitly initialize parsed subtitle cues from the resolved downloaded primary subtitle file. Sidebar cue population depended on later mpv source rediscovery, which could leave snapshots empty.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Added a regression test for YouTube auto-load sidebar cue refresh and wired the YouTube subtitle flow to explicitly refresh parsed subtitle cues from the resolved primary subtitle path after a successful load. Verified with targeted YouTube/sidebar/runtime tests plus typecheck.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,65 +0,0 @@
---
id: TASK-227
title: 'Assess and address PR #31 latest CodeRabbit review round'
status: Done
assignee:
- codex
created_date: '2026-03-24 03:53'
updated_date: '2026-03-24 06:41'
labels:
- pr-review
- coderabbit
dependencies: []
references:
- >-
PR #31 feat: add app-owned YouTube subtitle flow with absPlayer-style
parsing
priority: medium
ordinal: 147500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Inspect the latest CodeRabbit review round on PR #31, verify each actionable comment against the current branch, implement only the valid fixes, add regression coverage where appropriate, and prepare thread replies for resolved or declined items.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Latest CodeRabbit comments on PR #31 are triaged into valid fixes vs non-actioned suggestions with rationale.
- [x] #2 Confirmed issues are fixed with regression coverage where appropriate.
- [x] #3 Relevant verification passes for the touched areas.
- [x] #4 PR reply notes are ready for each addressed or declined latest-review comment.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Verify the five latest CodeRabbit inline comments against the current branch and separate valid bugs from non-actioned suggestions.
2. Add failing regression coverage for confirmed issues in launcher playback tests, CLI YouTube flow error handling, and renderer YouTube picker disabled-state behavior.
3. Implement the minimal production fixes for the confirmed issues, plus remove the duplicate overlay Anki initialization if still redundant.
4. Inspect the YouTube primary-subtitle failure timer wiring to decide whether a code change is warranted in this round or whether a technical reply declining the comment is more correct.
5. Run targeted Bun tests for the touched files and prepare concise PR thread replies for each latest-review comment.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Triaged the latest PR #31 CodeRabbit round: five inline comments were current action items; implemented all five. Strengthened the launcher playback test fixture so YouTube pause coverage no longer piggybacks on generic overlay auto-pause settings.
Added regression tests for CLI YouTube flow rejection handling, no-track picker disabled-state restoration, and app-owned YouTube notification suppression while subtitle acquisition is still in flight.
Implemented `runAsyncWithOsd(...)` handling for `args.youtubePlay`, kept no-track picker controls disabled after failed continue attempts, added `setAppOwnedFlowInFlight(...)` to the YouTube primary-subtitle notification runtime with main-process wiring around `runYoutubePlaybackFlowMain(...)`, and removed the duplicate `initializeOverlayAnkiIntegrationCore(...)` call from `initializeOverlayRuntime()`.
Verification passed: `bun test launcher/commands/playback-command.test.ts src/core/services/cli-command.test.ts src/renderer/modals/youtube-track-picker.test.ts src/main/runtime/youtube-primary-subtitle-notification.test.ts` and `bun run typecheck`.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Assessed the latest CodeRabbit review round on PR #31 and implemented all five current inline action items. Strengthened the launcher playback regression test so app-owned YouTube pause behavior is asserted independently from generic overlay auto-pause settings, wrapped the CLI `youtubePlay` branch in the existing `runAsyncWithOsd(...)` path so probe/download/startup failures surface in logs and OSD, kept the no-track YouTube picker controls disabled after rejected continue attempts, suppressed the generic primary-subtitle failure timer while the app-owned YouTube flow is still probing/downloading and restarted it only after the flow settles, and removed the duplicate overlay Anki initialization from `initializeOverlayRuntime()`.
Verification passed with `bun test launcher/commands/playback-command.test.ts src/core/services/cli-command.test.ts src/renderer/modals/youtube-track-picker.test.ts src/main/runtime/youtube-primary-subtitle-notification.test.ts` and `bun run typecheck`.
Prepared thread-reply notes for the five latest inline comments; did not post them because GitHub replies are an external side effect.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,64 +0,0 @@
---
id: TASK-228
title: 'Assess and address PR #31 subsequent CodeRabbit review round'
status: Done
assignee:
- codex
created_date: '2026-03-24 04:10'
updated_date: '2026-03-24 06:41'
labels:
- pr-review
- coderabbit
dependencies: []
references:
- >-
PR #31 feat: add app-owned YouTube subtitle flow with absPlayer-style
parsing
- 'commit cdb12827 fix: address PR #31 latest review follow-ups'
priority: medium
ordinal: 146500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Inspect the subsequent CodeRabbit review round on PR #31 after commit cdb12827, verify each newly reported issue against the current branch, implement the valid fixes with regression coverage where appropriate, and prepare/update PR thread replies.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 New CodeRabbit comments after cdb12827 are triaged into valid fixes vs declined suggestions with rationale.
- [x] #2 Confirmed issues are fixed with regression coverage where appropriate.
- [x] #3 Relevant verification passes for the touched areas.
- [x] #4 PR threads are updated for the addressed comments.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Verify the new CodeRabbit comments after cdb12827 and separate valid bugs from refactor-only suggestions.
2. Add failing regression coverage for the valid runtime issues: `track.selected` fallback in the YouTube primary-subtitle notifier and consistent no-track handling in the picker.
3. Inspect existing test seams for the `main.ts` flow-entry guards; if lightweight coverage exists, add it before patching. Otherwise apply the minimal `main.ts` fixes and rely on typecheck plus targeted regression tests around the affected runtime helpers.
4. Implement the confirmed fixes: picker re-entry guard, broader `inFlight` cleanup, `track.selected` fallback, and a single canonical `hasTracks` check.
5. Run targeted tests/typecheck and update the new PR threads with landed fix refs.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Triaged the post-cdb12827 CodeRabbit round. Implemented the 4 concrete follow-ups: manual picker re-entry guard, broader `setAppOwnedFlowInFlight(...)` cleanup, `track.selected` fallback in the YouTube primary-subtitle notifier, and a single canonical `payloadHasTracks(...)` helper in the picker. Also took the adjacent `replaceChildren()` cleanup while touching the same picker paths.
Verification passed: `bun test src/main/runtime/youtube-primary-subtitle-notification.test.ts src/renderer/modals/youtube-track-picker.test.ts launcher/commands/playback-command.test.ts src/core/services/cli-command.test.ts` and `bun run typecheck`.
Updated the new CodeRabbit inline threads with landed fix refs and left a top-level PR comment noting the large refactor suggestions are intentionally out of scope for this bugfix round.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Assessed the subsequent CodeRabbit review round on PR #31 after cdb12827 and applied the valid follow-ups in commit 5f6f93cd. Added a guard in `openYoutubeTrackPickerFromPlayback()` so the manual picker cannot re-enter while another YouTube flow session is active, widened the app-owned in-flight suppression to cover synchronous Windows mpv bootstrap and connect failures, taught the primary-subtitle notifier to honor `track.selected` before `sid` arrives, and unified the pickers subtitle-availability logic behind `payloadHasTracks(...)` while swapping node clearing to `replaceChildren()`.
Verification passed with `bun test src/main/runtime/youtube-primary-subtitle-notification.test.ts src/renderer/modals/youtube-track-picker.test.ts launcher/commands/playback-command.test.ts src/core/services/cli-command.test.ts` and `bun run typecheck`.
Updated the latest inline CodeRabbit threads plus a top-level PR comment summarizing the round and explicitly deferred the large refactor suggestions as non-blocking maintainability nits.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,55 +0,0 @@
---
id: TASK-229
title: 'Address PR #31 final CodeRabbit picker test follow-up'
status: Done
assignee:
- codex
created_date: '2026-03-24 04:27'
updated_date: '2026-03-24 06:41'
labels:
- pr-review
- coderabbit
dependencies: []
references:
- >-
PR #31 feat: add app-owned YouTube subtitle flow with absPlayer-style
parsing
- >-
CodeRabbit comment on src/renderer/modals/youtube-track-picker.test.ts
global restoration / harness duplication
priority: medium
ordinal: 145500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Fix the remaining CodeRabbit comment on the YouTube picker test file by restoring absent globals correctly and reducing repeated test harness setup so global stubbing is consistent and isolated.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Picker tests restore `window`, `document`, and `CustomEvent` without leaving undefined-valued globals behind.
- [x] #2 Repeated picker test setup is consolidated enough to remove the current review complaint.
- [x] #3 Relevant picker tests pass and PR thread is updated.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a failing regression around global restoration semantics in the YouTube picker test harness.
2. Extract shared DOM/environment helpers and restore logic using delete when globals were originally absent.
3. Re-run focused tests and typecheck, then commit/push and reply on the PR thread.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Latest CodeRabbit comment targets youtube-track-picker.test.ts harness cleanup and correct restoration of global properties.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Addressed the last PR #31 CodeRabbit comment by refactoring the YouTube picker test harness to use shared DOM/env helpers, restoring absent globals via delete semantics, adding a regression for cleanup behavior, and pushing commit 039e2f56 with focused picker tests plus typecheck passing.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,57 +0,0 @@
---
id: TASK-231
title: Restore controller input while subtitle sidebar is open
status: Done
assignee:
- '@codex'
created_date: '2026-03-24 00:15'
updated_date: '2026-03-24 00:15'
labels:
- bug
- controller
- subtitle-sidebar
- overlay
dependencies: []
references:
- /home/sudacode/projects/japanese/SubMiner/src/renderer/renderer.ts
- /home/sudacode/projects/japanese/SubMiner/src/renderer/controller-interaction-blocking.ts
- /home/sudacode/projects/japanese/SubMiner/src/renderer/controller-interaction-blocking.test.ts
priority: high
ordinal: 54900
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
When keyboard-only mode is active, opening the subtitle sidebar should not disable controller navigation and lookup/mining controls. Restore controller input while the sidebar is open, while keeping true modal dialogs blocking controller actions.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Opening the subtitle sidebar does not block controller input for keyboard-only mode actions.
- [x] #2 Controller-select/debug and other true modal dialogs still block controller actions while open.
- [x] #3 Focused regression coverage exists for the sidebar-open controller gating rule.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Root cause: renderer gamepad polling used the broad `isAnyModalOpen()` check as its interaction gate, and that list includes `subtitleSidebarModalOpen`. The subtitle sidebar is non-modal for controller usage, so gamepad input was being suppressed whenever the sidebar was visible.
Fixed by extracting a dedicated controller-interaction blocking helper that excludes the subtitle sidebar but keeps the existing blocking behavior for true modal dialogs.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Restored controller input while the subtitle sidebar is open by switching gamepad polling to a dedicated modal-blocking rule that leaves the sidebar controller-passive. Added a regression test covering the sidebar-open exception and preserving hard blocks for actual modal dialogs.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,66 +0,0 @@
---
id: TASK-232
title: Trim release package size by pruning duplicate and source-only assets
status: Done
assignee:
- '@codex'
created_date: '2026-03-24 12:05'
updated_date: '2026-03-24 12:30'
labels:
- release
- packaging
priority: medium
ordinal: 54700
dependencies: []
references:
- /home/sudacode/projects/japanese/SubMiner/package.json
- /home/sudacode/projects/japanese/SubMiner/src/release-workflow.test.ts
- /home/sudacode/projects/japanese/SubMiner/src/core/services/texthooker.ts
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Reduce packaged release artifact size without changing user-visible functionality by pruning files that are duplicated between `app.asar` and `extraResources`, excluding source/test/doc-only trees from Electron packaging, and trimming obviously non-runtime vendored payload.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Electron packaging excludes repo content that is source-only, test-only, docs-only, or duplicated by `extraResources`.
- [x] #2 Release packaging tests cover the new exclusion rules.
- [x] #3 Verification includes at least targeted release-packaging tests and one packaging-oriented validation step.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Completed scope:
- Exclude `assets`, `plugin`, and `vendor/yomitan-jlpt-vocab` from `files` because they are already staged via `extraResources`.
- Exclude `dist` sourcemaps/tests, repo docs/tests/packaging metadata, and stats source leftovers from `files`.
- Exclude non-runtime `vendor/texthooker-ui` payload such as `public/`, `.vscode/`, and package metadata.
- Exclude Linux musl libsql binary from packaged app payload for AppImage-focused savings.
Verification:
- `bun test src/release-workflow.test.ts`
- `bun run build`
- `node_modules/.bin/electron-builder --linux dir --publish never`
- `node_modules/.bin/electron-builder --linux AppImage --publish never`
Observed result:
- `release/linux-unpacked/resources/app.asar` dropped from about `100 MB` to `29 MB`.
- `release/SubMiner-0.9.0.AppImage` dropped from about `256 MB` to `194 MB`.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Trimmed Electron packaging so release artifacts no longer bundle duplicated `extraResources`, source/test/doc-only repo content, non-runtime `texthooker-ui` files, or the Linux musl libsql binary. Added release-packaging regression coverage and verified the Linux package shrink with fresh local builds.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,35 +0,0 @@
---
id: TASK-243
title: 'Assess and address PR #36 latest CodeRabbit review round'
status: Done
assignee: []
created_date: '2026-03-29 07:39'
updated_date: '2026-03-29 07:41'
labels:
- code-review
- pr-36
dependencies: []
references:
- 'https://github.com/ksyasuda/SubMiner/pull/36'
priority: high
ordinal: 3600
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Inspect the latest CodeRabbit review round on PR #36, verify each actionable comment against the current branch, implement the confirmed fixes, and verify the touched paths.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Confirmed review comments are implemented or explicitly deferred with rationale.
- [ ] #2 Touched paths are verified with the smallest sufficient test/build lane.
- [ ] #3 Current PR feedback is reduced to resolved or intentionally deferred suggestions.
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Addressed the confirmed latest CodeRabbit review items on PR #36. `scripts/run-coverage-lane.ts` now uses the Bun-style `import.meta.main` entrypoint check with a local ts-ignore to preserve the repo's CommonJS typecheck settings. `src/core/services/immersion-tracker/maintenance.ts` no longer shadows the imported `nowMs` helper in retention functions. `src/main.ts` now centralizes the startup-mode predicates behind a shared helper and releases `resolvedSource.cleanup` on the cached-subtitle fast path so materialized sources do not leak.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,35 +0,0 @@
---
id: TASK-244
title: 'Assess and address PR #36 latest CodeRabbit review round 2'
status: Done
assignee: []
created_date: '2026-03-29 08:09'
updated_date: '2026-03-29 08:10'
labels:
- code-review
- pr-36
dependencies: []
references:
- 'https://github.com/ksyasuda/SubMiner/pull/36'
priority: high
ordinal: 3610
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Inspect the newest CodeRabbit review round on PR #36, verify the actionable comment against the current branch, implement the confirmed fix, and verify the touched path.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 The actionable review comment is implemented or explicitly deferred with rationale.
- [ ] #2 Touched path is verified with the smallest sufficient test lane.
- [ ] #3 Current PR feedback is reduced to resolved or intentionally deferred suggestions.
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Addressed the actionable latest CodeRabbit comment on PR #36. `src/core/services/immersion-tracker/maintenance.ts` now skips retention deletions when a window is disabled with `Infinity`, so `toDbMs(...)` is only called for finite retention values. Added a regression test in `maintenance.test.ts` that verifies disabled retention windows preserve session events, telemetry, and sessions while returning zero deletions.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,39 +0,0 @@
---
id: TASK-272
title: 'Assess and address PR #40 CodeRabbit review follow-ups'
status: Done
assignee: []
created_date: '2026-04-03 07:52'
updated_date: '2026-04-03 08:04'
labels:
- coderabbit
- review
- launcher
milestone: 'PR #40'
dependencies: []
references:
- 'https://github.com/ksyasuda/SubMiner/pull/40'
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Implement the valid CodeRabbit findings on PR #40 and keep the Windows mpv shortcut / first-run setup flow consistent end to end.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Windows binary resolution does not return install directories as executable candidates
- [ ] #2 Launch-mpv arg parsing preserves space-separated mpv option values and target separation
- [ ] #3 Windows mpv launch args keep the final input-ipc-server and script-opts socket path in sync when custom values are supplied
- [ ] #4 First-run setup navigation swallows stale or invalid custom-scheme actions without navigating away
- [ ] #5 Setup messaging and footer copy reflect configReady, plugin, and dictionary gates consistently
- [ ] #6 Regression tests cover the fixed behaviors
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Addressed CodeRabbit follow-ups for PR #40. Hardened launcher binary discovery on Windows and PATH resolution, fixed launch-mpv argument parsing for value-bearing flags, synced custom Windows mpv IPC socket values into script opts, and tightened first-run setup messaging/navigation to handle stale actions and blocker copy. Verified with `bun test src/main-entry-runtime.test.ts src/main/runtime/windows-mpv-launch.test.ts src/main/runtime/first-run-setup-window.test.ts launcher/mpv.test.ts`.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

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

View File

@@ -1,8 +0,0 @@
---
id: m-2
title: 'Mining Workflow Upgrades'
---
## Description
Future user-facing workflow improvements that directly improve discoverability, previewability, and mining control without depending on speculative platform integrations like OCR, marketplace infrastructure, or cloud sync.

View File

@@ -2,10 +2,9 @@
id: TASK-143
title: Keep character dictionary auto-sync non-blocking during startup
status: Done
assignee:
- codex
assignee: []
created_date: '2026-03-09 01:45'
updated_date: '2026-03-23 03:22'
updated_date: '2026-03-18 05:28'
labels:
- dictionary
- startup
@@ -18,7 +17,7 @@ references:
- >-
/home/sudacode/projects/japanese/SubMiner/src/main/runtime/current-media-tokenization-gate.ts
priority: high
ordinal: 144500
ordinal: 38500
---
## Description
@@ -34,20 +33,8 @@ Keep character dictionary auto-sync running in parallel during startup without d
- [x] #3 Regression coverage verifies auto-sync builds before the gate and only mutates Yomitan after the gate resolves.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a regression test for startup autoplay release surviving delayed mpv readiness or late subtitle refresh after dictionary sync.
2. Harden the autoplay-ready release path so paused startup keeps retrying until mpv is actually released or media changes, without resuming user-paused playback later.
3. Keep the existing character-dictionary revisit fixes and paused-startup OSD fixes aligned with the autoplay change, then run targeted runtime tests and typecheck.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Added a small current-media tokenization gate in main runtime. Media changes reset the gate, the first tokenization-ready event marks it ready, and auto-sync now waits on that gate only before Yomitan dictionary inspection/import/settings updates. Snapshot generation and merged ZIP build still run immediately in parallel.
2026-03-20: User reports startup remains paused after annotations/tokenization are visible and only resumes after character-dictionary generation/import finishes. Investigating autoplay-ready release regression vs dictionary sync completion refresh.
2026-03-20: Added startup autoplay retry-budget helper so paused startup retries cover the full plugin gate window instead of only ~2.8s. Verification: bun test src/main/runtime/startup-autoplay-release-policy.test.ts src/main/runtime/character-dictionary-auto-sync.test.ts src/main/runtime/startup-osd-sequencer.test.ts src/main/runtime/character-dictionary-auto-sync-completion.test.ts; bun run typecheck; bun run test:fast; bun run test:env; bun run build; bun run test:smoke:dist; runtime-compat verifier passed at .tmp/skill-verification/subminer-verify-20260320-022106-nM28Nk. Pending real installed-app/mpv validation.
<!-- SECTION:NOTES:END -->

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