11 Commits

Author SHA1 Message Date
b63936055a chore(release): 0.6.2 2026-03-12 00:35:44 -07:00
beb48ab0cb Allow first-run setup completion with external Yomitan profile
- Treat `yomitan.externalProfilePath` as satisfying dictionary setup in launcher and app first-run flow
- Reopen setup if an externally-completed setup later runs without external profile and no internal dictionaries
- Bump setup state to v3 with `yomitanSetupMode` migration and update setup UI/docs/tests
2026-03-12 00:28:01 -07:00
6ff89b9227 Harden Yomitan runtime state and profile policy handling
- Centralize external-profile read-only behavior in a shared Yomitan profile policy
- Clear parser/extension runtime state via dedicated helpers on load failures and reloads
- Prevent opening Yomitan settings when the Yomitan session is unavailable
- Add focused runtime-policy and state-clearing regression tests
2026-03-11 22:56:50 -07:00
c9d5f6b6e3 Disable character-dictionary features in external profile mode
- Gate character-dictionary runtime, auto-sync, annotations, and CLI generation when `yomitan.externalProfilePath` is set
- Return explicit disabled reason for blocked character-dictionary generation in read-only external-profile mode
- Fix default config bootstrap to seed `config.jsonc` when config dir exists but config file is missing
- Update tests, changelog fragment, and docs to reflect the new behavior
2026-03-11 21:02:00 -07:00
6569eaa0ac merge with main 2026-03-11 20:33:33 -07:00
9cbc3fc335 Harden Yomitan read-only logging and extract overlay options
- Redact skipped Yomitan write log values (paths to basename, titles hidden)
- Extract overlay BrowserWindow option builder for direct unit testing
- Document and test `externalProfilePath` tilde (`~`) home expansion
2026-03-11 20:33:11 -07:00
ae44477a69 Wire Yomitan session into overlay window creation
- Pass `yomitanSession` through overlay window factory deps
- Set BrowserWindow `webPreferences.session` when session is available
- Extend tests to verify session plumbing across runtime/factory layers
2026-03-11 20:33:11 -07:00
aa569272db Expand Yomitan external profile tilde paths to home directory
- Normalize `yomitan.externalProfilePath` so `~` and `~/...` resolve to the current user home directory
- Add coverage for tilde expansion in integration config tests
- Update Yomitan config docs and tighten settings opener test assertion
2026-03-11 20:33:11 -07:00
504793eaed Harden Yomitan settings open flow for external profile mode
- Return status from `openYomitanSettings` and show user-facing warning when external read-only profile mode blocks settings
- Thread `yomitanSession` through settings runtime/opener deps so settings window uses the active session
- Expand tests for session forwarding and external profile path propagation
- Move AniList setup/token/CLI docs into the AniList section in configuration docs
2026-03-11 20:33:11 -07:00
a64af69365 Clarify Yomitan external profile path for Linux GSM overlay
- Document Linux GameSentenceMiner overlay default: `~/.config/gsm_overlay`
- Add inline example for `yomitan.externalProfilePath` in integration option docs
2026-03-11 20:33:11 -07:00
3ee71139a6 Add read-only external Yomitan profile support
- add `yomitan.externalProfilePath` config and default/template wiring
- load Yomitan from an external Electron profile/session when configured
- disable SubMiner Yomitan writes/settings UI in external-profile mode and update docs/tests
2026-03-11 20:33:11 -07:00
996 changed files with 12127 additions and 108946 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 +0,0 @@
---
name: 'subminer-change-verification'
description: 'Compatibility shim. Canonical SubMiner change verification workflow now lives in the repo-local subminer-workflow plugin.'
---
# Compatibility Shim
Canonical source:
- `plugins/subminer-workflow/skills/subminer-change-verification/SKILL.md`
Canonical helper scripts:
- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh`
- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh`
When this shim is invoked:
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.

View File

@@ -1,13 +0,0 @@
#!/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"
if [[ ! -x "$TARGET" ]]; then
echo "Missing canonical script: $TARGET" >&2
exit 1
fi
exec "$TARGET" "$@"

View File

@@ -1,13 +0,0 @@
#!/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"
if [[ ! -x "$TARGET" ]]; then
echo "Missing canonical script: $TARGET" >&2
exit 1
fi
exec "$TARGET" "$@"

View File

@@ -1,18 +0,0 @@
---
name: 'subminer-scrum-master'
description: 'Compatibility shim. Canonical SubMiner scrum-master workflow now lives in the repo-local subminer-workflow plugin.'
---
# Compatibility Shim
Canonical source:
- `plugins/subminer-workflow/skills/subminer-scrum-master/SKILL.md`
When this shim is invoked:
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.
This shim exists so existing repo references and prompts keep resolving during the migration to the repo-local plugin workflow.

View File

@@ -27,23 +27,17 @@ jobs:
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') }}
key: ${{ runner.os }}-bun-${{ hashFiles('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
run: bun install --frozen-lockfile
- name: Lint changelog fragments
run: bun run changelog:lint
- name: Lint stats (formatting)
run: bun run lint:stats
- name: Enforce pull request changelog fragments (`skip-changelog` label bypass)
if: github.event_name == 'pull_request'
run: bun run changelog:pr-check --base-ref "origin/${{ github.base_ref }}" --head-ref "HEAD" --labels "${{ join(github.event.pull_request.labels.*.name, ',') }}"
@@ -55,22 +49,9 @@ jobs:
- name: Verify generated config examples
run: bun run verify:config-example
- name: Internal docs knowledge-base checks
run: bun run test:docs:kb
- 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

@@ -9,6 +9,9 @@ concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
permissions:
contents: write
jobs:
quality-gate:
runs-on: ubuntu-latest
@@ -29,19 +32,13 @@ jobs:
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') }}
key: ${{ runner.os }}-bun-${{ hashFiles('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
run: bun install --frozen-lockfile
- name: Build (TypeScript check)
run: bun run typecheck
@@ -49,16 +46,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
@@ -99,17 +86,14 @@ jobs:
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') }}
key: ${{ runner.os }}-bun-${{ hashFiles('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
run: bun install --frozen-lockfile
- name: Build texthooker-ui
run: |
@@ -157,10 +141,9 @@ jobs:
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') }}
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }}
restore-keys: |
${{ runner.os }}-bun-
@@ -185,9 +168,7 @@ jobs:
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
- name: Install dependencies
run: |
bun install --frozen-lockfile
cd stats && bun install --frozen-lockfile
run: bun install --frozen-lockfile
- name: Build texthooker-ui
run: |
@@ -232,17 +213,14 @@ jobs:
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') }}
key: ${{ runner.os }}-bun-${{ hashFiles('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
run: bun install --frozen-lockfile
- name: Build texthooker-ui
shell: powershell
@@ -266,8 +244,6 @@ jobs:
release:
needs: [build-linux, build-macos, build-windows]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -342,15 +318,7 @@ jobs:
- name: Get version from tag
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
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: Verify changelog is ready for tagged release
run: bun run changelog:check --version "${{ steps.version.outputs.VERSION }}"
@@ -395,130 +363,3 @@ jobs:
for asset in "${artifacts[@]}"; do
gh release upload "${{ steps.version.outputs.VERSION }}" "$asset" --clobber
done
aur-publish:
needs: [release]
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Get version from tag
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
- name: Check AUR publish prerequisites
id: aur_prereqs
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
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"
- 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"
- 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: |
set -euo pipefail
version="${{ steps.version.outputs.VERSION }}"
install -dm755 .tmp/aur-release-assets
gh release download "$version" \
--dir .tmp/aur-release-assets \
--pattern "SubMiner-${version#v}.AppImage" \
--pattern "subminer" \
--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 }}"
version_no_v="${version_no_v#v}"
cp packaging/aur/subminer-bin/PKGBUILD aur-subminer-bin/PKGBUILD
cp packaging/aur/subminer-bin/.SRCINFO aur-subminer-bin/.SRCINFO
bash scripts/update-aur-package.sh \
--pkg-dir aur-subminer-bin \
--version "${{ steps.version.outputs.VERSION }}" \
--appimage ".tmp/aur-release-assets/SubMiner-${version_no_v}.AppImage" \
--wrapper ".tmp/aur-release-assets/subminer" \
--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
run: |
set -euo pipefail
if git diff --quiet -- PKGBUILD .SRCINFO; then
echo "AUR packaging already up to date."
exit 0
fi
git config user.name "github-actions[bot]"
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."

21
.gitignore vendored
View File

@@ -1,15 +1,11 @@
# Dependencies
node_modules/
# Superpowers brainstorming
.superpowers/
# Electron build output
out/
dist/
release/
build/yomitan/
coverage/
# Launcher build artifact (produced by make build-launcher)
/subminer
@@ -26,7 +22,9 @@ Thumbs.db
.idea/
*.swp
*.swo
**/CLAUDE.md
environment.toml
**/CLAUDE.md
.env
.vscode/*
@@ -37,21 +35,6 @@ docs/.vitepress/cache/
docs/.vitepress/dist/
tests/*
.worktrees/
.tmp/
.codex/*
.agents/*
!.agents/skills/
.agents/skills/*
!.agents/skills/subminer-change-verification/
!.agents/skills/subminer-scrum-master/
.agents/skills/subminer-change-verification/*
!.agents/skills/subminer-change-verification/SKILL.md
!.agents/skills/subminer-change-verification/scripts/
.agents/skills/subminer-change-verification/scripts/*
!.agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh
!.agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh
.agents/skills/subminer-scrum-master/*
!.agents/skills/subminer-scrum-master/SKILL.md
favicon.png
.claude/*
!stats/public/favicon.png

View File

@@ -1,29 +1,17 @@
# AGENTS.MD
## Internal Docs
Start here, then leave this file.
- Internal system of record: [`docs/README.md`](./docs/README.md)
- Architecture map: [`docs/architecture/README.md`](./docs/architecture/README.md)
- Workflow map: [`docs/workflow/README.md`](./docs/workflow/README.md)
- Verification lanes: [`docs/workflow/verification.md`](./docs/workflow/verification.md)
- Knowledge-base rules: [`docs/knowledge-base/README.md`](./docs/knowledge-base/README.md)
- Release guide: [`docs/RELEASING.md`](./docs/RELEASING.md)
`docs-site/` is user-facing. Do not treat it as the canonical internal source of truth.
## Quick Start
- Init workspace: `git submodule update --init --recursive`
- Install deps: `make deps` or `bun install` plus `(cd vendor/texthooker-ui && bun install --frozen-lockfile)`
- Fast dev loop: `make dev-watch`
- Full local run: `bun run dev`
- Verbose Electron debug: `electron . --start --dev --log-level debug`
- Read [`docs-site/development.md`](./docs-site/development.md) and [`docs-site/architecture.md`](./docs-site/architecture.md) before substantial changes; follow them unless task requires deviation.
- Init workspace: `git submodule update --init --recursive`.
- Install deps: `make deps` or `bun install` plus `(cd vendor/texthooker-ui && bun install --frozen-lockfile)`.
- Fast dev loop: `make dev-watch`.
- Full local run: `bun run dev`.
- Verbose Electron debug: `electron . --start --dev --log-level debug`.
## Build / Test
- Runtime/package manager: Bun (`packageManager: bun@1.3.5`)
- Use repo package manager/runtime only: Bun (`packageManager: bun@1.3.5`).
- Default handoff gate:
`bun run typecheck`
`bun run test:fast`
@@ -33,37 +21,59 @@ Start here, then leave this file.
- If `docs-site/` changed, also run:
`bun run docs:test`
`bun run docs:build`
- Prefer `make pretty` and `bun run format:check:src`
- Formatting: prefer `make pretty` and `bun run format:check:src`; use `bun run format` only intentionally.
- Keep verification observable; capture failing command + exact error in notes/handoff.
## Change-Specific Checks
- Config/schema/defaults: `bun run test:config`; if template/defaults changed, `bun run generate:config-example`
- Launcher/plugin: `bun run test:launcher` or `bun run test:env`
- Runtime-compat / dist-sensitive: `bun run test:runtime:compat`
- Docs-only: `bun run docs:test`, then `bun run docs:build`
- Config/schema/defaults changes: run `bun run test:config`; if config template/defaults changed, run `bun run generate:config-example`.
- Launcher/plugin changes: run `bun run test:launcher` or `bun run test:env`; use `bun run test:launcher:smoke:src` for focused launcher e2e checks.
- Runtime-compat or compiled/dist-sensitive changes: run `bun run test:runtime:compat`.
- Docs-only changes: at least `bun run docs:test` if docs behavior/assertions changed; `bun run docs:build` before handoff.
## Sensitive Files
## Generated / Sensitive Files
- Launcher source of truth: `launcher/*.ts`
- Generated launcher artifact: `dist/launcher/subminer`; never hand-edit it
- Repo-root `./subminer` is stale; do not revive it
- `bun run build` rebuilds bundled Yomitan from `vendor/subminer-yomitan`
- Do not change signing/packaging identifiers unless the task explicitly requires it
- Launcher source of truth: `launcher/*.ts`.
- Generated launcher artifact: `dist/launcher/subminer`; never hand-edit it.
- Repo-root `./subminer` is stale artifact path; do not revive/use it.
- `bun run build` rebuilds bundled Yomitan from `vendor/subminer-yomitan`; check submodules before debugging build failures.
- Avoid changing packaging/signing identifiers (`build.appId`, mac entitlements, signing-related settings) unless task explicitly requires it.
## Release / PR Notes
## Docs
- User-visible PRs need one fragment in `changes/*.md`
- CI enforces `bun run changelog:lint` and `bun run changelog:pr-check`
- PR review helpers:
- `gh pr view --json number,title,url --jq '"PR #\\(.number): \\(.title)\\n\\(.url)"'`
- `gh api repos/:owner/:repo/pulls/<num>/comments --paginate`
- Docs site lives in-repo under [`docs-site/`](./docs-site/).
- Update docs for new/breaking behavior; no ship with stale docs.
- Make sure [`docs-site/changelog.md`](./docs-site/changelog.md) is updated on each release.
## Runtime Notes
## PR Feedback
- Use Codex background for long jobs; tmux only when persistence/interaction is required
- CI red: `gh run list/view`, rerun, fix, repeat until green
- TypeScript: keep files small; follow existing patterns
- Swift: use workspace helper/daemon; validate `swift build` + tests
- Active PR: `gh pr view --json number,title,url --jq '"PR #\\(.number): \\(.title)\\n\\(.url)"'`.
- PR comments: `gh pr view …` + `gh api …/comments --paginate`.
- Replies: cite fix + file/line; resolve threads only after fix lands.
## Changelog
- User-visible PRs: add one fragment in `changes/*.md`.
- Fragment format:
`type: added|changed|fixed|docs|internal`
`area: <short-area>`
blank line
`- bullet`
- `changes/README.md`: instructions only; generator ignores it.
- No release-note entry wanted: use PR label `skip-changelog`.
- CI runs `bun run changelog:lint` + `bun run changelog:pr-check` on PRs.
- Release prep: `bun run changelog:build`, review `CHANGELOG.md` + `release/release-notes.md`, commit generated changelog + fragment deletions, then tag.
- Release CI expects committed changelog entry already present; do not rely on tag job to invent notes.
## Flow & Runtime
- Use Codex background for long jobs; tmux only for interactive/persistent (debugger/server).
- CI red: `gh run list/view`, rerun, fix, push, repeat til green.
## Language/Stack Notes
- Swift: use workspace helper/daemon; validate `swift build` + tests; keep concurrency attrs right.
- TypeScript: use repo PM; keep files small; follow existing patterns.
<!-- BACKLOG.MD MCP GUIDELINES START -->
@@ -83,6 +93,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,187 +1,5 @@
# Changelog
## Unreleased
### Fixed
- AniList: Stopped post-watch tracking from sending a second progress update when the current episode was already satisfied by a ready retry item in the same watch-completion pass.
## 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
- Release: Seed the AUR checkout with the repo `.SRCINFO` template before rewriting metadata so tagged releases do not depend on prior AUR state.
## v0.6.4 (2026-03-15)
### Internal
- Release: Reworked AUR metadata generation to update `.SRCINFO` directly instead of depending on runner `makepkg`, fixing tagged release publishing for `subminer-bin`.
## v0.6.3 (2026-03-15)
### Changed
- Overlay: Expanded the `Alt+C` controller modal into an inline config/remap flow with preferred-controller saving and per-action learn mode for buttons, triggers, and stick directions.
### Internal
- Workflow: Hardened the `subminer-scrum-master` skill to explicitly answer whether docs updates and changelog fragments are required before handoff.
- Release: Automate `subminer-bin` AUR package updates from the tagged release workflow.
## v0.6.2 (2026-03-12)
### Changed

View File

@@ -1 +0,0 @@
AGENTS.md

View File

@@ -1,4 +1,4 @@
.PHONY: help deps build build-launcher install build-linux build-macos build-macos-unsigned clean install-linux install-macos install-windows install-plugin uninstall uninstall-linux uninstall-macos uninstall-windows print-dirs pretty lint ensure-bun generate-config generate-example-config dev-start dev-start-macos dev-watch dev-watch-macos dev-toggle dev-stop
.PHONY: help deps build build-launcher install build-linux build-macos build-macos-unsigned clean install-linux install-macos install-windows install-plugin uninstall uninstall-linux uninstall-macos uninstall-windows print-dirs pretty ensure-bun generate-config generate-example-config dev-start dev-start-macos dev-watch dev-watch-macos dev-toggle dev-stop
APP_NAME := subminer
THEME_SOURCE := assets/themes/subminer.rasi
@@ -69,12 +69,11 @@ help:
" generate-config Generate ~/.config/SubMiner/config.jsonc from centralized defaults" \
"" \
"Other targets:" \
" deps Install JS dependencies (root + stats + texthooker-ui)" \
" deps Install JS dependencies (root + texthooker-ui)" \
" uninstall-linux Remove Linux install artifacts" \
" uninstall-macos Remove macOS install artifacts" \
" uninstall-windows Remove Windows mpv plugin artifacts" \
" print-dirs Show resolved install locations" \
" lint Lint stats (format check)" \
"" \
"Variables:" \
" PREFIX=... Override wrapper install prefix (default: $$HOME/.local)" \
@@ -105,7 +104,6 @@ print-dirs:
deps:
@$(MAKE) --no-print-directory ensure-bun
@bun install
@cd stats && bun install --frozen-lockfile
@cd vendor/texthooker-ui && bun install --frozen-lockfile
ensure-bun:
@@ -113,10 +111,6 @@ ensure-bun:
pretty: ensure-bun
@bun run format:src
@bun run format:stats
lint: ensure-bun
@bun run lint:stats
build:
@printf '%s\n' "[INFO] Detected platform: $(PLATFORM)"

294
README.md
View File

@@ -1,177 +1,43 @@
<div align="center">
<img src="assets/SubMiner.png" width="169" alt="SubMiner logo">
<h1>SubMiner</h1>
<strong>Look up words, mine to Anki, and enrich cards with context — without leaving mpv.</strong>
<br /><br />
<img src="assets/SubMiner.png" width="160" alt="SubMiner logo">
# SubMiner
## Turn mpv into a sentence-mining workstation.
Look up words with Yomitan, export to Anki in one key, track your immersion — all without leaving mpv.
[![License: GPL v3](https://img.shields.io/badge/license-GPLv3-1a1a2e?style=flat-square)](https://www.gnu.org/licenses/gpl-3.0)
[![Platform](https://img.shields.io/badge/platform-Linux%20·%20macOS%20·%20Windows-1a1a2e?style=flat-square)](https://github.com/ksyasuda/SubMiner)
[![Docs](https://img.shields.io/badge/docs-docs.subminer.moe-e6a817?style=flat-square)](https://docs.subminer.moe)
[![AUR](https://img.shields.io/aur/version/subminer-bin?style=flat-square&color=1a1a2e)](https://aur.archlinux.org/packages/subminer-bin)
[![SubMiner demo](./assets/minecard.webp)](./assets/minecard.mp4)
[![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)
</div>
## How It Works
SubMiner runs as an invisible Electron overlay on top of mpv. Subtitles render as an interactive layer. Move your cursor over any word and trigger a [Yomitan](https://github.com/yomidevs/yomitan) lookup. Press one key to snapshot the sentence, audio, and screenshot into Anki via AnkiConnect.
## Features
### Dictionary Lookups
Yomitan runs inside the overlay. Trigger a lookup on any word for full dictionary popups — definitions, pitch accent, frequency data — without ever leaving mpv.
<br />
<div align="center">
<img src="docs-site/public/screenshots/yomitan-lookup.png" width="800" alt="Yomitan dictionary popup over annotated subtitles in mpv">
[![SubMiner demo (Animated preview)](./assets/minecard.webp)](./assets/minecard.mp4)
</div>
<br>
<br />
### Instant Anki Mining
## What it does
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.
SubMiner is an Electron overlay that sits on top of mpv. It turns your video player into a full sentence-mining workstation:
<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">
</div>
- **Look up words as you watch** — Yomitan dictionary popups on hover or keyboard-driven token-by-token navigation
- **One-key Anki mining** — Creates cards with sentence, audio, screenshot, and translation; optional local AnkiConnect proxy auto-enriches Yomitan cards instantly
- **Reading annotations** — N+1 targeting, frequency-dictionary highlighting, JLPT underlining, and character name dictionary for anime/manga proper nouns
- **Subtitle tools** — Download from Jimaku, sync with alass/ffsubsync
- **Jellyfin & AniList integration** — Remote playback, cast device mode, and automatic episode progress tracking
- **Texthooker & API** — Built-in texthooker page and annotated websocket feed for external clients
<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.
<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">
</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.
<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">
</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.
<br>
### Integrations
<table>
<tr>
<td><b>YouTube</b></td>
<td>Auto-loaded yt-dlp subtitle tracks at startup with 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 | Optional |
| -------------- | --------------------------------------- | ---------------------------------------------------------- |
| **Player** | [`mpv`](https://mpv.io) with IPC socket | — |
| **Processing** | `ffmpeg`, `mecab` + `mecab-ipadic` | `guessit` (AniSkip), `alass` / `ffsubsync` (subtitle sync) |
| **Media** | — | `yt-dlp`, `chafa`, `ffmpegthumbnailer` |
| **Selection** | — | `fzf` / `rofi` |
> [!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 |
| ----------------------------------- | ------------------------ | ------------- |
| `hyprctl` or `xdotool` + `xwininfo` | Accessibility permission | No extra deps |
<details>
<summary><b>Arch Linux</b></summary>
```bash
paru -S --needed mpv ffmpeg mecab-git mecab-ipadic
# Optional
paru -S --needed 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
paru -S --needed xdotool xorg-xwininfo
```
</details>
<details>
<summary><b>macOS</b></summary>
```bash
brew install mpv ffmpeg mecab mecab-ipadic
# Optional
brew install 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`.
For MeCab, install [MeCab for Windows](https://taku910.github.io/mecab/#download) with the UTF-8 dictionary.
</details>
---
## Quick Start
## Quick start
### 1. Install
<details>
<summary><b>Arch Linux (AUR)</b></summary>
**Arch Linux (AUR):**
Install [`subminer-bin`](https://aur.archlinux.org/packages/subminer-bin) from the AUR. It installs the packaged AppImage plus the `subminer` wrapper:
```bash
paru -S subminer-bin
@@ -180,83 +46,83 @@ paru -S subminer-bin
Or manually:
```bash
git clone https://aur.archlinux.org/subminer-bin.git && cd subminer-bin && makepkg -si
git clone https://aur.archlinux.org/subminer-bin.git
cd subminer-bin
makepkg -si
```
</details>
<details>
<summary><b>Linux (AppImage)</b></summary>
**Linux (AppImage):**
```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 \
&& chmod +x ~/.local/bin/subminer
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
chmod +x ~/.local/bin/subminer
```
> [!NOTE]
> The `subminer` wrapper uses a [Bun](https://bun.sh) shebang. Make sure `bun` is on your `PATH`.
</details>
**macOS (DMG/ZIP):** download the latest packaged build from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest) and drag `SubMiner.app` into `/Applications`.
<details>
<summary><b>macOS</b></summary>
**Windows (Installer/ZIP):** download the latest `SubMiner-<version>.exe` installer or portable `.zip` from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest). Keep `mpv` installed and available on `PATH`.
Download the latest DMG or ZIP from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest) and drag `SubMiner.app` into `/Applications`.
**From source** — initialize submodules first (`git submodule update --init --recursive`). Bundled Yomitan is built from the `vendor/subminer-yomitan` submodule into `build/yomitan` during `bun run build`, so source builds only need Bun for the JS toolchain. Packaged macOS and Windows installs do not require Bun. Windows installer builds go through `electron-builder`; its bundled `app-builder-lib` NSIS templates already use the third-party `WinShell` plugin for shortcut AppUserModelID assignment, and the `WinShell.dll` binary is supplied by electron-builder's cached `nsis-resources` bundle, so `bun run build:win` does not need a separate repo-local plugin install step. Full install guide: [docs.subminer.moe/installation#from-source](https://docs.subminer.moe/installation#from-source).
</details>
<details>
<summary><b>Windows</b></summary>
Download the latest installer or portable `.zip` from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest). Make sure `mpv` is on your `PATH`.
</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
Run the app. On first launch SubMiner starts in the system tray, creates a default config, and opens a setup popup to install the mpv plugin and configure Yomitan dictionaries.
### 3. Mine
### 2. Launch the app once
```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
# Linux
SubMiner.AppImage
```
On macOS, launch `SubMiner.app`. On Windows, launch `SubMiner.exe` from the Start menu or install directory.
On first launch, SubMiner:
- starts in the tray/background
- creates the default config directory and `config.jsonc`
- opens a compact setup popup
- can install the mpv plugin to the default mpv scripts location for you
- links directly to Yomitan settings so you can install dictionaries before finishing setup
### 3. Finish setup
- click `Install mpv plugin` if you want the default plugin auto-start flow
- click `Open Yomitan Settings` and install at least one dictionary
- click `Refresh status`
- click `Finish setup`
The mpv plugin step is optional. Yomitan must report at least one installed dictionary before setup can be completed.
### 4. Mine
```bash
subminer video.mkv # default plugin config auto-starts visible overlay + resumes playback when ready
subminer --start video.mkv # optional explicit overlay start when plugin auto_start=no
```
## Requirements
| Required | Optional |
| ------------------------------------------ | -------------------------------------------------- |
| `bun` (source builds, Linux `subminer`) | |
| `mpv` with IPC socket | `yt-dlp` |
| `ffmpeg` | `guessit` (better AniSkip title/episode detection) |
| `mecab` + `mecab-ipadic` | `fzf` / `rofi` |
| Linux: `hyprctl` or `xdotool` + `xwininfo` | `chafa`, `ffmpegthumbnailer` |
| macOS: Accessibility permission | |
Windows builds use native window tracking and do not require the Linux compositor helper tools.
## Documentation
Full guides on configuration, Anki setup, Jellyfin, immersion tracking, and more: **[docs.subminer.moe](https://docs.subminer.moe)**
---
For full guides on configuration, Anki, Jellyfin, and more, see [docs.subminer.moe](https://docs.subminer.moe). The VitePress source for that site lives in [`docs-site/`](./docs-site/).
## 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 the shoulders of [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 powered by [Jimaku.cc](https://jimaku.cc). Dictionary lookups via [Yomitan](https://github.com/yomidevs/yomitan), and JLPT tags from [yomitan-jlpt-vocab](https://github.com/stephenmk/yomitan-jlpt-vocab).
## License

View File

@@ -1,33 +0,0 @@
---
id: TASK-175
title: Address latest PR 19 review comments
status: In Progress
assignee: []
created_date: '2026-03-15 10:25'
labels:
- pr-review
- stats-dashboard
dependencies: []
references:
- src/core/services/ipc.ts
- src/core/services/stats-server.ts
- src/core/services/immersion-tracker/__tests__/query.test.ts
- src/core/services/stats-window-runtime.ts
- src/core/services/stats-window.test.ts
- src/shared/ipc/contracts.ts
- src/main.ts
priority: medium
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Validate the latest automated review comments on PR #19 against the current branch, implement the technically valid fixes, and document any items intentionally left unchanged.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 Validated the latest PR #19 review comments against current branch behavior and existing architecture
- [ ] #2 Implemented the accepted fixes with regression coverage where it fits
- [ ] #3 Documented which latest review items were intentionally not changed because they were already addressed or not technically warranted
<!-- AC:END -->

View File

@@ -1,53 +0,0 @@
---
id: TASK-143
title: Keep character dictionary auto-sync non-blocking during startup
status: Done
assignee:
- codex
created_date: '2026-03-09 01:45'
updated_date: '2026-03-23 03:22'
labels:
- dictionary
- startup
- performance
dependencies: []
references:
- /home/sudacode/projects/japanese/SubMiner/src/main.ts
- >-
/home/sudacode/projects/japanese/SubMiner/src/main/runtime/character-dictionary-auto-sync.ts
- >-
/home/sudacode/projects/japanese/SubMiner/src/main/runtime/current-media-tokenization-gate.ts
priority: high
ordinal: 144500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Keep character dictionary auto-sync running in parallel during startup without delaying playback. Only tokenization readiness should gate playback; character dictionary import/settings updates should wait until tokenization is already ready and then refresh annotations afterward.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Character dictionary snapshot/build work can run immediately during startup.
- [x] #2 Yomitan dictionary mutation work waits until current-media tokenization is ready.
- [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 -->

View File

@@ -1,66 +0,0 @@
---
id: TASK-162
title: Normalize packaged Linux paths to canonical SubMiner directories
status: Done
assignee:
- codex
created_date: '2026-03-11 08:28'
updated_date: '2026-03-18 05:28'
labels:
- linux
- packaging
- docs
dependencies: []
references:
- launcher/mpv.ts
- launcher/picker.ts
- plugin/subminer/binary.lua
- plugin/subminer.conf
- docs-site/installation.md
- docs-site/launcher-script.md
- README.md
priority: medium
ordinal: 116500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Align packaged Linux path conventions so system-installed assets use canonical `SubMiner` directories and match runtime auto-detection. Cover AppImage binary discovery, rofi theme discovery/docs, and related path references while preserving lowercase names only for the launcher wrapper, rofi theme filename, and mpv Lua plugin/conf.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Launcher/runtime path discovery prefers canonical packaged Linux locations that use `SubMiner` casing for shared data and config directories.
- [x] #2 Tests cover the expected packaged Linux discovery paths for the AppImage and rofi theme search behavior.
- [x] #3 User-facing docs reference the canonical packaged Linux locations consistently.
- [x] #4 Lowercase names remain only where intentionally required for the launcher wrapper, rofi theme filename, and mpv Lua plugin/conf.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add failing launcher tests for canonical packaged Linux discovery paths: /usr/lib/subminer/SubMiner.AppImage via PATH symlink flow and /usr/share/SubMiner/themes/subminer.rasi for rofi theme lookup.
2. Update launcher runtime path discovery to prefer canonical packaged Linux shared-data locations using SubMiner casing.
3. Update plugin auto-detection comments and binary search defaults so packaged Linux paths stay consistent with launcher/runtime expectations.
4. Update user-facing docs to reference canonical SubMiner-cased config/share paths while keeping lowercase names only for the launcher wrapper, rofi theme filename, and mpv Lua plugin/conf.
5. Run targeted launcher tests plus docs checks.
Remaining work (2026-03-15):
- binary.lua: add lowercase fallback candidates /usr/bin/subminer and /usr/local/bin/subminer after existing title-case entries
- launcher tests: add findAppBinary Linux candidates and findRofiTheme /usr/share + /usr/local/share tests
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
2026-03-15: Adding launcher tests for Linux packaged path discovery (findAppBinary + findRofiTheme). Implementing in mpv.test.ts and new picker.test.ts following node:test / assert/strict patterns from mpv.test.ts.
2026-03-15: AC#2 complete. Added findAppBinary tests (3) to launcher/mpv.test.ts and findRofiTheme tests (4) to new launcher/picker.test.ts. All 76 launcher tests pass. Added picker.test.ts to test:launcher:src script.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
## Completed changes\n\n### `plugin/subminer/binary.lua`\nAdded lowercase fallback candidates after existing title-case entries in the non-Windows `find_binary()` search list:\n- `/usr/local/bin/subminer` (after `/usr/local/bin/SubMiner`)\n- `/usr/bin/subminer` (after `/usr/bin/SubMiner`)\n\n### `plugin/subminer.conf`\nUpdated the comment documenting the Linux binary search list to include the two new lowercase candidates.\n\n### `launcher/mpv.test.ts`\nAdded 3 new tests for `findAppBinary` Linux candidates:\n- Resolves `~/.local/bin/SubMiner.AppImage` when it exists\n- Resolves `/opt/SubMiner/SubMiner.AppImage` when `~/.local/bin` candidate absent\n- Finds `subminer` on PATH when AppImage candidates absent\n\n### `launcher/picker.test.ts` (new file)\nAdded 4 tests for `findRofiTheme` Linux packaged paths:\n- Resolves `/usr/local/share/SubMiner/themes/subminer.rasi`\n- Resolves `/usr/share/SubMiner/themes/subminer.rasi` when `/usr/local/share` absent\n- Resolves `$XDG_DATA_HOME/SubMiner/themes/subminer.rasi` when set\n- Resolves `~/.local/share/SubMiner/themes/subminer.rasi` when `XDG_DATA_HOME` unset\n\n### `package.json`\nAdded `launcher/picker.test.ts` to `test:launcher:src` file list.\n\n## Verification\n- `launcher-plugin` lane: passed (76 launcher tests, 524 fast tests — all green)\n\n## Policy checks\n- Docs update required? No — docs already reflected canonical paths.\n- Changelog fragment required? Yes — user-visible fix to plugin binary auto-detection. Fragment should be added under `changes/`.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,94 +0,0 @@
---
id: TASK-165
title: Make controller configuration easier with inline remapping modal
status: To Do
assignee:
- Codex
created_date: '2026-03-13 00:10'
updated_date: '2026-03-13 00:10'
labels:
- enhancement
- renderer
- overlay
- input
- config
dependencies:
- TASK-159
references:
- src/renderer/modals/controller-select.ts
- src/renderer/modals/controller-debug.ts
- src/renderer/handlers/gamepad-controller.ts
- src/renderer/index.html
- src/renderer/style.css
- src/renderer/utils/dom.ts
- src/preload.ts
- src/core/services/ipc.ts
- src/main.ts
- src/types.ts
- src/config/definitions/defaults-core.ts
- src/config/definitions/options-core.ts
- config.example.jsonc
- docs/plans/2026-03-13-overlay-controller-config-remap-design.md
- docs/plans/2026-03-13-overlay-controller-config-remap.md
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Replace the current controller-selection-only modal with a denser controller configuration surface that keeps device selection and adds inline controller remapping. The new flow should feel like emulator configuration: pick an overlay action, arm capture, then press the matching controller button, trigger, d-pad direction, or stick direction to bind it. Keep the current overlay-local renderer architecture, preserve controller gating to keyboard-only mode, and retain the separate raw debug modal for troubleshooting.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [ ] #1 `Alt+C` opens a controller modal that includes both preferred-controller selection and controller-config editing in one surface.
- [ ] #2 Controller device selection uses a compact dropdown or equivalent compact picker instead of the current full-height device list.
- [ ] #3 Each remappable controller action shows its current binding and supports learn/capture, clear, and reset-to-default flows.
- [ ] #4 Learn mode captures the next fresh controller input edge or stick/d-pad direction, not a held/stale input.
- [ ] #5 Captured bindings can represent non-standard controllers without depending only on the browser's standard semantic button names.
- [ ] #6 Updated bindings persist through the existing config pipeline and take effect in the renderer without restart unless a field explicitly requires reopen/reload.
- [ ] #7 Existing controller behavior remains gated to keyboard-only mode except for the controller action that toggles keyboard-only mode itself.
- [ ] #8 Renderer/config/IPC regression tests cover the new modal layout, capture flow, persistence, and runtime mapping behavior.
- [ ] #9 Docs/config example explain the new controller-config flow and when to use the debug modal.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add the design doc and implementation plan for inline controller remapping, tied to a new backlog task instead of reopening the already-completed base controller-support task.
2. Expand controller config types/defaults/template output so action bindings can store captured input descriptors, not only semantic button-name enums.
3. Extend preload/main/IPC write paths from preferred-controller-only saves to full controller-config patching needed by the modal.
4. Redesign the controller modal UI into a compact device picker plus action-binding editor with learn, clear, and reset affordances.
5. Add renderer capture state and a learn-mode runtime that waits for neutral-to-active transitions before saving a binding.
6. Update the gamepad runtime to resolve the new stored descriptors into actions while preserving current gating and repeat/deadzone behavior.
7. Keep the raw debug modal as a separate advanced surface; optionally expose copyable input-descriptor text for troubleshooting.
8. Add focused regression tests first, then run the maintained gate needed for docs/config/renderer/main changes.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Planning only in this pass.
Current-state findings:
- `src/renderer/modals/controller-select.ts` only persists `preferredGamepadId` / `preferredGamepadLabel`.
- `src/preload.ts`, `src/core/services/ipc.ts`, and `src/main.ts` only expose a narrow save path for preferred controller, not general controller config writes.
- `src/renderer/handlers/gamepad-controller.ts` currently resolves actions from semantic button bindings plus a few axis slots; this is fine for defaults but too narrow for emulator-style learn mode on non-standard controllers.
- `src/renderer/modals/controller-debug.ts` already provides the raw input surface needed for troubleshooting and for validating capture behavior.
Recommended direction:
- keep `Alt+C` as the single controller-config entrypoint
- keep `Alt+Shift+C` as raw debug
- introduce stored input descriptors for discrete bindings so learn mode can capture buttons, triggers, d-pad directions, and stick directions directly
- defer per-controller profiles; keep one global binding set plus preferred-controller selection for this pass
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Planned follow-up work to make controller configuration materially easier than the current “pick preferred device” modal. The proposed change keeps existing controller runtime/debug foundations, but upgrades the selection modal into a compact controller-config surface with inline learn-mode remapping and persistent binding storage.
Main architectural change in scope: move from semantic-button-only binding storage toward captured input descriptors so the UI can reliably learn from buttons, triggers, d-pad directions, and stick directions on non-standard controllers.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,65 +0,0 @@
---
id: TASK-165
title: Rewrite SubMiner agentic testing automation plan
status: Done
assignee: []
created_date: '2026-03-13 04:45'
updated_date: '2026-03-16 05:13'
labels:
- planning
- testing
- agents
dependencies: []
references:
- /home/sudacode/projects/japanese/SubMiner/testing-plan.md
- >-
/home/sudacode/projects/japanese/SubMiner/.agents/skills/subminer-change-verification/SKILL.md
- >-
/home/sudacode/projects/japanese/SubMiner/.agents/skills/subminer-scrum-master/SKILL.md
documentation:
- /home/sudacode/projects/japanese/SubMiner/docs-site/development.md
- /home/sudacode/projects/japanese/SubMiner/docs-site/architecture.md
ordinal: 23500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Replace the current generic Electron/mpv testing plan with a SubMiner-specific plan that uses the existing skills as the source of truth, treats real launcher/plugin/mpv runtime verification as primary, and defines a non-interference contract for parallel agent work.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 `testing-plan.md` is rewritten for SubMiner rather than a generic Electron+mpv app
- [x] #2 The plan keeps `subminer-scrum-master` and `subminer-change-verification` as the primary orchestration and verification entrypoints
- [x] #3 The plan defines real launcher/plugin/mpv runtime verification as the authoritative lane for runtime bug claims
- [x] #4 The plan defines explicit session isolation and non-interference rules for parallel agent work
- [x] #5 The plan defines artifact/reporting expectations and phased rollout, with synthetic/headless verification clearly secondary to real-runtime verification
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Review the existing testing plan and compare it against current SubMiner architecture, verification lanes, and skills.
2. Replace the generic Electron/mpv harness framing with a SubMiner-specific control plane centered on existing skills.
3. Define the authoritative real-runtime lane, session isolation rules, concurrency classes, and reporting contract.
4. Sanity-check the rewritten document against current repo docs and skill contracts before handoff.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Rewrote `testing-plan.md` around existing `subminer-scrum-master` and `subminer-change-verification` responsibilities instead of proposing a competing new top-level testing skill.
Set real launcher/plugin/mpv/runtime verification as the authoritative lane for runtime bug claims and made synthetic/headless verification explicitly secondary.
Defined session-scoped paths, unique mutable resources, concurrency classes, and an exclusive lease for conflicting real-runtime verification to prevent parallel interference.
Sanity-checked the final document by inspecting the rewritten file content and diff.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Rewrote `testing-plan.md` into a SubMiner-specific agentic verification plan. The new document keeps `subminer-scrum-master` and `subminer-change-verification` as the primary orchestration and verification entrypoints, treats the real launcher/plugin/mpv/runtime path as authoritative for runtime bug claims, and defines a hard non-interference contract for parallel work through session isolation and an exclusive real-runtime lease. The plan now also includes an explicit reporting schema, capture policy, phased rollout, and a clear statement that true parallel full-app instances are not a phase-1 requirement. Verification for this task was a document sanity pass against the current repo docs, skills, and the resulting file diff.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,76 +0,0 @@
---
id: TASK-166
title: Prevent AUR upgrade cache collisions for unversioned release assets
status: Done
assignee:
- Codex
created_date: '2026-03-17 18:10'
updated_date: '2026-03-18 05:28'
labels:
- release
- packaging
- linux
dependencies:
- TASK-165
references:
- /home/sudacode/projects/japanese/SubMiner/.github/workflows/release.yml
- /home/sudacode/projects/japanese/SubMiner/scripts/update-aur-package.sh
- /home/sudacode/projects/japanese/SubMiner/scripts/update-aur-package.test.ts
- >-
/home/sudacode/projects/japanese/SubMiner/packaging/aur/subminer-bin/PKGBUILD
- >-
/home/sudacode/projects/japanese/SubMiner/packaging/aur/subminer-bin/.SRCINFO
priority: medium
ordinal: 107500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Fix the AUR release metadata generated by the tagged-release workflow so end-user upgrades do not reuse stale cached downloads for unversioned `subminer` and `subminer-assets.tar.gz` source names.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 AUR packaging generated for a new `pkgver` uses versioned local source aliases for the non-versioned GitHub release assets.
- [x] #2 The package install step references the versioned local launcher filename correctly.
- [x] #3 Regression coverage fails if metadata generation reintroduces stable cache-colliding source aliases.
- [x] #4 Targeted verification records the commands run and results.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a failing regression test around `scripts/update-aur-package.sh` output for versioned local source aliases.
2. Update the repo AUR template and `.SRCINFO` rewrite logic to stamp versioned alias names for `subminer` and `subminer-assets`.
3. Verify the generated metadata and targeted workflow/package tests, then record results here.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Root cause: the AUR package used stable local source aliases for the unversioned `subminer` and `subminer-assets.tar.gz` GitHub release assets. `makepkg`/AUR helpers can reuse those cached filenames across upgrades, so a stale cached download survives into a newer `pkgver` and then fails checksum validation.
Patched the repo AUR template to version the local cache aliases:
- `subminer-${pkgver}::.../subminer`
- `subminer-assets-${pkgver}.tar.gz::.../subminer-assets.tar.gz`
Updated `package()` to install the versioned local wrapper filename, and updated `scripts/update-aur-package.sh` so the generated `.SRCINFO` stamps matching concrete versioned aliases for release automation.
Added regression assertions in `scripts/update-aur-package.test.ts` covering both versioned source aliases and the launcher install path, then watched that test fail before the patch and pass after it.
Verification:
- `bun test scripts/update-aur-package.test.ts`
- `bash -n scripts/update-aur-package.sh && bash -n packaging/aur/subminer-bin/PKGBUILD`
- `bun run typecheck`
- `bun run test:fast`
- `bun run test:env`
- `bun run build`
- `bun run test:smoke:dist`
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
The tagged-release AUR metadata path now emits versioned local source aliases for the non-versioned GitHub release assets, preventing stale `makepkg` cache reuse across `subminer-bin` upgrades. The change is covered by a regression test and passed the repo's maintained verification gate.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,52 +0,0 @@
---
id: TASK-167
title: Track shared SubMiner agent skills in git and clean up ignore rules
status: Done
assignee: []
created_date: '2026-03-13 05:46'
updated_date: '2026-03-16 05:13'
labels:
- git
- agents
- repo-hygiene
dependencies: []
references:
- /home/sudacode/projects/japanese/SubMiner/.gitignore
- >-
/home/sudacode/projects/japanese/SubMiner/.agents/skills/subminer-change-verification/SKILL.md
- >-
/home/sudacode/projects/japanese/SubMiner/.agents/skills/subminer-scrum-master/SKILL.md
documentation:
- /home/sudacode/projects/japanese/SubMiner/testing-plan.md
ordinal: 21500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Adjust the repository ignore rules so the shared SubMiner agent skill files can be committed while keeping unrelated local agent state ignored. Also ensure generated local verification artifacts like `.tmp/` do not pollute git status.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Root ignore rules allow the shared SubMiner skill files under `.agents/skills/` to be tracked without broadly unignoring local agent state
- [x] #2 The changed shared skill files appear in git status as trackable files after the ignore update
- [x] #3 Local generated verification artifact directories remain ignored so git status stays clean
- [x] #4 The updated ignore rules are minimal and scoped to the repo-shared skill files
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Updated `.gitignore` to keep `.agents` ignored by default while narrowly unignoring the repo-shared SubMiner skill files and verifier scripts.
Added `.tmp/` to the root ignore rules so local verification artifacts stop polluting `git status`.
Verified the result with `git status --untracked-files=all` and `git check-ignore -v`, confirming the shared skill files are now trackable and `.tmp/` remains ignored.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Adjusted the root `.gitignore` so the shared SubMiner agent skill files can be committed cleanly without broadly unignoring local agent state. The repo now tracks the shared `subminer-change-verification` skill files and the `subminer-scrum-master` skill doc, while `.tmp/` is ignored so generated verification artifacts do not pollute git status. Verified with `git status --untracked-files=all` and `git check-ignore -v` that the intended skill files are commit-ready and `.tmp/` remains ignored.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,39 +0,0 @@
---
id: TASK-168
title: Document immersion stats dashboard and config
status: Done
assignee:
- codex
created_date: '2026-03-12 22:53'
updated_date: '2026-03-16 05:13'
labels:
- docs
- immersion
dependencies: []
priority: medium
ordinal: 24500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Refresh user-facing docs for the new immersion stats dashboard so README, docs-site pages, changelog notes, and generated config examples describe how to access the dashboard and which `stats.*` settings control it.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 README mentions the new stats surface in product-facing feature/docs copy.
- [x] #2 Docs explain how to access the stats dashboard in-app and via localhost, and document the `stats` config block.
- [x] #3 Changelog/release-note input includes the new stats dashboard.
- [x] #4 Generated config examples include the new `stats` section.
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Updated README and the docs-site immersion/config/mining/shortcut/homepage copy to describe the new stats dashboard, including the overlay toggle (`stats.toggleKey`, default `Backquote`) and the localhost browser UI (`http://127.0.0.1:5175` by default).
Added a changelog fragment for the stats dashboard release notes and extended the config template sections so regenerated `config.example.jsonc` artifacts now include the `stats` block.
Verified with `bun run test:config`, `bun run generate:config-example`, `bun run docs:test`, `bun run docs:build`, and `bun run changelog:lint`.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,80 +0,0 @@
---
id: TASK-169
title: Add anime-level immersion metadata and link videos
status: Done
assignee:
- codex
created_date: '2026-03-13 19:34'
updated_date: '2026-03-16 05:13'
labels:
- immersion
- stats
- database
- anilist
milestone: m-1
dependencies: []
references:
- >-
/home/sudacode/projects/japanese/SubMiner/docs/plans/2026-03-13-immersion-anime-metadata-design.md
- >-
/home/sudacode/projects/japanese/SubMiner/docs/plans/2026-03-13-immersion-anime-metadata.md
ordinal: 20500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add first-class anime metadata to the immersion tracker so stats can group sessions and videos by anime, season, and episode instead of relying only on per-video canonical titles. The new model should deduplicate anime-level metadata across rewatches and multiple files, use guessit-first filename parsing with built-in parser fallback, and create provisional anime rows even when AniList lookup fails.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 The immersion schema includes a new anime-level table plus additive video linkage/parsed metadata fields needed for anime, season, and episode stats.
- [x] #2 Media ingest creates or reuses anime rows, stores parsed season/episode metadata on videos, and upgrades provisional anime rows when AniList data becomes available.
- [x] #3 Query surfaces expose anime-level aggregation suitable for library/detail/episode stats without breaking current video/session queries.
- [x] #4 Focused regression coverage exists for schema/storage/query/service behavior, including provisional anime rows and guessit-first parser fallback behavior.
- [x] #5 Verification covers the SQLite immersion lane and any broader lanes required by the touched runtime/query files.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add red tests for the new schema shape in the SQLite immersion lane before changing storage code.
2. Implement `imm_anime` plus additive `imm_videos` metadata fields and focused storage helpers for provisional anime creation and AniList upgrade.
3. Add a guessit-first parser helper with built-in fallback and wire media ingest to persist anime/video metadata during `handleMediaChange(...)`.
4. Add anime-level query surfaces for library/detail/episode aggregation and expose them only where needed.
5. Run focused SQLite verification first, then broader verification lanes only if touched runtime/API files require them.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
2026-03-13: Design approved in-thread. Initial scope excluded migration/backfill work, but implementation was corrected in-thread to add a legacy DB migration/backfill path based on filename parsing.
2026-03-13: Detailed implementation plan written at `docs/plans/2026-03-13-immersion-anime-metadata.md`.
2026-03-13: Task 6 export/API work was intentionally skipped because no current stats API/UI consumer needs the anime query surface yet, and widening the contract would have touched unrelated dirty stats files.
2026-03-13: Verification commands run:
- `bun test src/core/services/immersion-tracker/storage-session.test.ts`
- `bun test src/core/services/immersion-tracker/metadata.test.ts`
- `bun test src/core/services/immersion-tracker-service.test.ts`
- `bun test src/core/services/immersion-tracker/__tests__/query.test.ts`
- `bun run test:immersion:sqlite:src`
- `bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh src/core/services/immersion-tracker/storage.ts src/core/services/immersion-tracker/storage-session.test.ts src/core/services/immersion-tracker/metadata.ts src/core/services/immersion-tracker/metadata.test.ts src/core/services/immersion-tracker/query.ts src/core/services/immersion-tracker/types.ts src/core/services/immersion-tracker/__tests__/query.test.ts src/core/services/immersion-tracker-service.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/storage.ts src/core/services/immersion-tracker/storage-session.test.ts src/core/services/immersion-tracker/metadata.ts src/core/services/immersion-tracker/metadata.test.ts src/core/services/immersion-tracker/query.ts src/core/services/immersion-tracker/types.ts src/core/services/immersion-tracker/__tests__/query.test.ts src/core/services/immersion-tracker-service.ts src/core/services/immersion-tracker-service.test.ts`
2026-03-13: Verification results:
- `bun run test:immersion:sqlite:src`: passed
- verifier lane selection: `core`
- verifier result: passed (`bun run typecheck`, `bun run test:fast`)
- verifier artifacts: `.tmp/skill-verification/subminer-verify-20260313-214533-Ciw3L0/`
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Added `imm_anime`, additive `imm_videos` anime/parser metadata fields, and a legacy migration/backfill path that links existing videos to provisional anime rows from parsed filenames.
Added focused storage helpers for normalized anime identity reuse, later AniList upgrades, and per-video season/episode/parser metadata linking. Media ingest now parses and links anime metadata during `handleMediaChange(...)`.
Added anime-level query surfaces for library/detail/episode aggregation and regression coverage for schema, migration, storage, parser fallback, service ingest wiring, and anime stats queries.
Verified with the focused SQLite lane plus verifier-selected `core` coverage (`typecheck`, `test:fast`). No stats API/UI export was added yet because there is no current consumer for the new anime query surface.
<!-- SECTION:FINAL_SUMMARY: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,39 +0,0 @@
---
id: TASK-170
title: Fix imm_words POS filtering and add stats cleanup maintenance command
status: Done
assignee: []
created_date: '2026-03-13 00:00'
updated_date: '2026-03-18 05:31'
labels: []
milestone: m-1
dependencies: []
priority: high
ordinal: 9010
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
`imm_words` is currently populated from raw subtitle text instead of tokenized subtitle metadata, so ignored functional/noise tokens leak into stats and no POS metadata is stored. Fix live persistence to follow the existing token annotation exclusion rules and add an on-demand stats cleanup command to remove stale bad vocabulary rows from the stats DB.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 New `imm_words` inserts use tokenized subtitle data, persist POS metadata, and skip tokens excluded by existing POS-based vocabulary ignore rules.
- [x] #2 `subminer stats cleanup` supports `-v` / `--vocab`, defaults to vocab cleanup, and removes stale bad `imm_words` rows on demand.
- [x] #3 Regression coverage exists for persistence filtering, cleanup behavior, and stats cleanup CLI wiring.
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed `imm_words` persistence so the tracker now consumes tokenized subtitle data, stores POS metadata (`part_of_speech`, `pos1`, `pos2`, `pos3`), preserves distinct surface/lemma fields (`word` vs `headword`) when tokenization provides them, and skips vocabulary rows excluded by the existing POS/noise rules instead of mining raw subtitle fragments. Added `subminer stats cleanup` with default vocab cleanup plus `-v/--vocab`; the cleanup pass now repairs stale `headword`, `reading`, and `part_of_speech` values, attempts best-effort MeCab backfill for legacy rows, and removes rows that still have no usable POS metadata or fail the vocab filters.
Verification:
- `bun run typecheck`
- `bun test src/core/services/immersion-tracker-service.test.ts src/core/services/immersion-tracker/__tests__/query.test.ts src/core/services/immersion-tracker/storage-session.test.ts launcher/parse-args.test.ts launcher/commands/command-modules.test.ts src/main/runtime/stats-cli-command.test.ts src/main/runtime/mpv-main-event-main-deps.test.ts src/core/services/cli-command.test.ts`
- `bun run docs:test`
- `bun run docs:build`
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,80 +0,0 @@
---
id: TASK-171
title: Add normalized immersion word and kanji occurrence tracking
status: Done
assignee:
- codex
created_date: '2026-03-14 11:30'
updated_date: '2026-03-16 05:13'
labels:
- immersion
- stats
- database
milestone: m-1
dependencies: []
references:
- >-
/home/sudacode/projects/japanese/SubMiner/docs/plans/2026-03-14-immersion-occurrence-tracking-design.md
- >-
/home/sudacode/projects/japanese/SubMiner/docs/plans/2026-03-14-immersion-occurrence-tracking.md
ordinal: 19500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add normalized occurrence tables for immersion-tracked words and kanji so stats can map vocabulary back to the exact anime, episode, timestamp, and subtitle line where each item appeared. Preserve repeated tokens within the same line via counted occurrences instead of deduping, while avoiding duplicated token text storage.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 The immersion schema adds normalized subtitle-line and counted occurrence tables for words and kanji, with additive migration support for existing databases.
- [x] #2 Subtitle-line tracking writes one subtitle-line row per seen line plus counted word/kanji occurrences linked back to the line, session, video, and anime context.
- [x] #3 Query surfaces can map a word or kanji back to anime/episode/line/timestamp rows without breaking current top-level vocabulary and kanji stats.
- [x] #4 Focused regression coverage exists for schema, counted occurrence persistence, and reverse-mapping queries.
- [x] #5 Verification covers the SQLite immersion lane and any broader lanes required by touched service/API files.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add red tests for new line/occurrence schema and migration shape in the SQLite immersion lane.
2. Add red tests for service-level subtitle persistence that writes one line row plus counted word/kanji occurrences.
3. Implement additive schema, write-path plumbing, and counted occurrence upserts with minimal disruption to existing aggregate tables.
4. Add reverse-mapping query surfaces for word and kanji occurrences, plus focused API/service exposure only where needed.
5. Run focused SQLite verification first, then broader verification only if touched runtime/API files require it.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
2026-03-14: Design approved in-thread. Chosen shape: `imm_subtitle_lines` plus counted bridge tables `imm_word_line_occurrences` and `imm_kanji_line_occurrences`, retaining repeated tokens within a line via `occurrence_count`.
2026-03-14: Implemented additive schema version bump to 7. `recordSubtitleLine(...)` now queues one normalized subtitle-line write that owns aggregate word/kanji upserts plus counted bridge-row inserts.
2026-03-14: Added reverse-mapping query surfaces for exact word triples and single kanji lookups. No stats API/UI consumer was widened in this change.
2026-03-14: Verification commands run:
- `bun test src/core/services/immersion-tracker-service.test.ts`
- `bun test src/core/services/immersion-tracker/storage-session.test.ts`
- `bun test src/core/services/immersion-tracker/__tests__/query.test.ts`
- `bun run typecheck`
- `bash .agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh src/core/services/immersion-tracker/types.ts src/core/services/immersion-tracker/storage.ts src/core/services/immersion-tracker/query.ts src/core/services/immersion-tracker-service.ts src/core/services/immersion-tracker/storage-session.test.ts src/core/services/immersion-tracker-service.test.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/types.ts src/core/services/immersion-tracker/storage.ts src/core/services/immersion-tracker/query.ts src/core/services/immersion-tracker-service.ts src/core/services/immersion-tracker/storage-session.test.ts src/core/services/immersion-tracker-service.test.ts src/core/services/immersion-tracker/__tests__/query.test.ts`
- `bun run test:immersion:sqlite:src`
2026-03-14: Verification results:
- targeted tracker/query tests: passed
- verifier lane selection: `core`
- verifier result: passed (`typecheck`, `test:fast`)
- verifier artifacts: `.tmp/skill-verification/subminer-verify-20260314-114630-abO7mb/`
- maintained immersion SQLite lane: passed
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Added normalized subtitle-line occurrence tracking to immersion stats with three additive tables: `imm_subtitle_lines`, `imm_word_line_occurrences`, and `imm_kanji_line_occurrences`.
`recordSubtitleLine(...)` now preserves repeated allowed tokens and repeated kanji within the same subtitle line via `occurrence_count`, while still updating canonical `imm_words` and `imm_kanji` aggregates.
Added reverse-mapping queries for exact word triples and kanji so callers can fetch anime/video/session/line/timestamp context for each occurrence without duplicating token text storage.
Verified with targeted tracker/query tests, `bun run typecheck`, verifier-selected `core` coverage, and the maintained `bun run test:immersion:sqlite:src` lane.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,76 +0,0 @@
---
id: TASK-172
title: Stabilize macOS fullscreen overlay layering and tracker flaps
status: Done
assignee:
- '@codex'
created_date: '2026-03-16 10:45'
updated_date: '2026-03-18 05:28'
labels:
- bug
- macos
- overlay
dependencies: []
references:
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/overlay-window.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/overlay-visibility.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/overlay-runtime-init.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/window-trackers/macos-tracker.ts
- /Users/sudacode/projects/japanese/SubMiner/src/main.ts
priority: high
ordinal: 54500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Fix the macOS fullscreen overlay bug where the visible overlay can slip behind mpv or become briefly hidden/non-interactable after tracker/helper churn. Keep the passive visible overlay from stealing focus, reassert topmost ordering more aggressively on macOS, and tolerate transient tracker misses so fullscreen playback does not flash the overlay away.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 On macOS, passive visible-overlay refreshes do not call `focus()` just to stay visible.
- [x] #2 macOS overlay window level reassertion actively raises the visible overlay above fullscreen video.
- [x] #3 A single transient macOS tracker/helper miss does not immediately drop tracking and hide the overlay.
- [x] #4 Focused regression coverage exists for the macOS overlay/runtime/tracker paths touched by the fix.
- [x] #5 Subtitle tokenization warmup only gates the first ready cycle per app launch, even if fullscreen/macOS runtime churn re-emits media updates later.
- [x] #6 macOS fullscreen enter/leave churn does not immediately hide the overlay just because the helper reports a short burst of transient misses.
- [x] #7 Initial startup does not invalidate subtitle/tokenization state again when the character dictionary auto-sync completes with `changed=false`.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Changed `src/core/services/overlay-visibility.ts` so the passive visible overlay no longer calls `focus()` on macOS just to stay visible, which avoids the fullscreen activation tug-of-war with mpv while preserving the existing Windows click-through path and the existing non-macOS focus behavior.
Changed `src/core/services/overlay-window.ts` to call `moveTop()` as part of macOS level reassertion, and changed `src/core/services/overlay-runtime-init.ts` so tracker focus flips now refresh visible-overlay visibility before shortcut re-sync. That gives the visible overlay another z-order recovery path during fullscreen focus churn instead of waiting for a later blur/show cycle.
Changed `src/window-trackers/macos-tracker.ts` to add a small helper runner seam plus consecutive-miss tolerance. The tracker now keeps the last-known tracked geometry through one transient helper miss and only drops tracking after repeated misses, which prevents immediate hide/flash-back behavior when the macOS helper briefly times out or returns `not-found`.
Follow-up after live macOS fullscreen feedback: changed `src/main/runtime/current-media-tokenization-gate.ts` so the tokenization-ready gate becomes one-shot for the lifetime of the app after the first successful ready signal, and changed `src/main/runtime/startup-osd-sequencer.ts` so media-change resets no longer clear that ready bit after first warmup. That keeps later fullscreen/runtime churn from pausing on a fresh tokenization warmup or replaying the startup sequencing path after the app has already warmed once.
Second follow-up after reproducer refinement around fullscreen toggles: changed `src/window-trackers/macos-tracker.ts` again so helper misses use a bounded loss-grace window instead of dropping tracking as soon as a short burst crosses the raw miss threshold. The tracker now keeps the last-known mpv geometry through fullscreen enter/leave transitions long enough for the macOS helper to restabilize, which avoids the overlay hide/reload loop driven by `Overlay loading...` during transient fullscreen churn.
Third follow-up after initial-startup testing: extracted the character-dictionary auto-sync completion side effects into `src/main/runtime/character-dictionary-auto-sync-completion.ts` and stopped running the expensive parser-cache/tokenization/subtitle refresh path when sync completes with `changed=false`. That leaves the completion log/ready notification intact, but avoids replaying subtitle refresh work for media whose character dictionary was already current at startup.
Added focused regressions in `src/core/services/overlay-visibility.test.ts`, `src/core/services/overlay-runtime-init.test.ts`, `src/window-trackers/macos-tracker.test.ts`, `src/main/runtime/current-media-tokenization-gate.test.ts`, and `src/main/runtime/startup-osd-sequencer.test.ts`. Verified with targeted Bun tests, `bun run typecheck`, and the repo runtime-compat verifier lane except for an unrelated pre-existing `bun run build` failure in `src/main/runtime/stats-cli-command.test.ts`.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Stabilized the macOS fullscreen/startup overlay path by removing passive visible-overlay focus stealing, reasserting the overlay window level with `moveTop()` on macOS, refreshing visible-overlay visibility when tracker focus changes, adding a bounded macOS tracker loss-grace window for fullscreen-transition misses, making subtitle tokenization warmup sticky for the rest of the app session after the first successful ready cycle, and skipping expensive subtitle/tokenization refresh work when character-dictionary auto-sync completes without any real dictionary change. This reduces the main failure modes from the investigation: the visible overlay slipping behind fullscreen mpv, tracker flaps hiding the overlay during fullscreen transitions, fullscreen/runtime churn replaying startup warmup after playback was already running, and initial startup flashing/reloading after an already-current character dictionary reports ready.
Verification:
- `bun test src/core/services/overlay-window.test.ts src/core/services/overlay-visibility.test.ts src/core/services/overlay-runtime-init.test.ts src/window-trackers/x11-tracker.test.ts src/window-trackers/macos-tracker.test.ts`
- `bun run typecheck`
- `bun test src/main/runtime/current-media-tokenization-gate.test.ts src/main/runtime/startup-osd-sequencer.test.ts src/main/runtime/character-dictionary-auto-sync.test.ts src/main/runtime/composers/mpv-runtime-composer.test.ts`
- `bun test src/window-trackers/macos-tracker.test.ts src/core/services/overlay-visibility.test.ts src/core/services/overlay-runtime-init.test.ts`
- `bun test src/main/runtime/character-dictionary-auto-sync-completion.test.ts src/main/runtime/character-dictionary-auto-sync.test.ts src/main/runtime/current-media-tokenization-gate.test.ts src/main/runtime/startup-osd-sequencer.test.ts`
- `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane runtime-compat src/core/services/overlay-visibility.ts src/core/services/overlay-window.ts src/core/services/overlay-runtime-init.ts src/window-trackers/macos-tracker.ts src/core/services/overlay-visibility.test.ts src/core/services/overlay-runtime-init.test.ts src/window-trackers/macos-tracker.test.ts`
- `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane runtime-compat src/main/runtime/current-media-tokenization-gate.ts src/main/runtime/startup-osd-sequencer.ts src/main/runtime/current-media-tokenization-gate.test.ts src/main/runtime/startup-osd-sequencer.test.ts` [build blocked by unrelated `src/main/runtime/stats-cli-command.test.ts` typing errors already present in workspace]
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,55 +0,0 @@
---
id: TASK-173
title: Deduplicate character dictionary auto-sync startup triggers
status: Done
assignee: []
created_date: '2026-03-16 11:05'
updated_date: '2026-03-16 11:20'
labels:
- bug
- character-dictionary
- startup
dependencies: []
references:
- /Users/sudacode/projects/japanese/SubMiner/src/main.ts
- /Users/sudacode/projects/japanese/SubMiner/src/main/runtime/mpv-client-event-bindings.ts
- /Users/sudacode/projects/japanese/SubMiner/src/main/runtime/mpv-main-event-actions.ts
- /Users/sudacode/projects/japanese/SubMiner/src/main/runtime/character-dictionary-auto-sync.ts
- /Users/sudacode/projects/japanese/SubMiner/src/main/character-dictionary-runtime.ts
priority: medium
ordinal: 36500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Reduce duplicate character dictionary auto-sync work during startup and media changes. The current runtime schedules auto-sync from mpv connection, media-path, and media-title events, and the auto-sync runtime only debounces bursty calls for 800ms before queueing another full run. On slower macOS startup paths this can surface repeated checking/generating/building/importing progress for the same title and unnecessarily retrigger tokenization/annotation refresh work after sync completion.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Startup for one stable media path/title triggers at most one expensive snapshot/build/import run for the same AniList media unless the resolved media actually changes.
- [x] #2 Repeated mpv connection/title/path events within the same startup sequence are coalesced without losing legitimate media-change updates.
- [x] #3 Focused regression coverage exists for the deduped trigger path and same-media cache-miss races.
<!-- AC:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Reduced the auto-sync trigger surface to mpv `media-path-change` only. `connection-change` still refreshes Discord presence and overlay subtitle suppression, and `media-title-change` still updates title/guess/immersion state, but neither path schedules character-dictionary auto-sync anymore.
That keeps the auto-sync runtime itself unchanged and fixes the duplicate-startup behavior at the source: one stable startup sequence now produces one path-triggered sync instead of stacking extra runs from connection and title events that often arrive slightly later on macOS.
Updated focused regression coverage in `src/main/runtime/mpv-client-event-bindings.test.ts` and `src/main/runtime/mpv-main-event-actions.test.ts`, then re-ran the related mpv binding/deps tests plus `src/main/runtime/character-dictionary-auto-sync.test.ts`.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed repeated character-dictionary startup work by stopping auto-sync scheduling from mpv `connection-change` and `media-title-change`; only `media-path-change` now triggers the sync. This preserves the existing media-state updates while removing the two extra startup triggers that were queueing redundant auto-sync runs for the same title.
Verification:
- `bun test src/main/runtime/mpv-client-event-bindings.test.ts src/main/runtime/mpv-main-event-actions.test.ts`
- `bun test src/main/runtime/mpv-client-event-bindings.test.ts src/main/runtime/mpv-main-event-actions.test.ts src/main/runtime/mpv-main-event-bindings.test.ts src/main/runtime/mpv-main-event-main-deps.test.ts src/main/runtime/character-dictionary-auto-sync.test.ts`
- `bun run typecheck`
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,42 +0,0 @@
---
id: TASK-173
title: Remove Avg Frequency metric from Vocabulary tab summary cards
status: Done
assignee: []
created_date: '2026-03-15 00:13'
updated_date: '2026-03-16 05:13'
labels:
- stats
- ui
milestone: m-1
dependencies: []
priority: low
ordinal: 17500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
User requested removing the Avg Frequency card/metric because it is not useful. Remove the UI card and stop computing/storing the summary field in dashboard summary shaping code.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Vocabulary tab no longer renders an "Avg Frequency" stat card.
- [x] #2 Vocabulary summary model no longer exposes or computes averageFrequency.
- [x] #3 Typecheck/tests covering dashboard summary and vocabulary tab pass.
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Removed the Vocabulary tab "Avg Frequency" card and deleted the corresponding `averageFrequency` field from `VocabularySummary` and `buildVocabularySummary`.
Verification run:
- `bun test stats/src/lib/dashboard-data.test.ts`
- `bun run typecheck`
- `bun run test:fast`
- `bun run build`
- `bun run test:env`
- `bun run test:smoke:dist`
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,68 +0,0 @@
---
id: TASK-174
title: Fix missing frequency highlights for merged tokenizer tokens
status: Done
assignee:
- codex
created_date: '2026-03-15 10:18'
updated_date: '2026-03-18 05:28'
labels:
- bug
- tokenizer
- frequency-highlighting
dependencies: []
references:
- /Users/sudacode/projects/japanese/SubMiner/src/core/services/tokenizer.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/tokenizer/parser-selection-stage.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/tokenizer/yomitan-parser-runtime.ts
- /Users/sudacode/projects/japanese/SubMiner/scripts/get_frequency.ts
- /Users/sudacode/projects/japanese/SubMiner/scripts/test-yomitan-parser.ts
priority: high
ordinal: 115500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Frequency highlighting can miss words that should color within the configured top-X limit when tokenizer candidate selection keeps merged Yomitan units that combine a content word with trailing function text. The annotation stage then conservatively clears frequency for the whole merged token, so visible high-frequency words lose highlighting. The standalone debug CLIs are also failing to initialize the shared Yomitan runtime, which blocks reliable repro for this class of bug.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Tokenizer no longer drops frequency highlighting for content words in merged-token cases where a better scanning parse candidate would preserve highlightable tokens.
- [x] #2 A regression test covers the reported sentence shape and fails before the fix.
- [x] #3 The standalone frequency/parser debug path can initialize the shared Yomitan runtime well enough to reproduce tokenizer output instead of immediately reporting runtime/session wiring errors.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a regression test for the reported merged-token frequency miss, centered on Yomitan scanning candidate selection and downstream frequency annotation.
2. Update tokenizer candidate selection so merged content+function tokens do not win over candidates that preserve highlightable content tokens.
3. Repair the standalone frequency/parser debug scripts so their Electron/Yomitan runtime wiring matches current shared runtime expectations.
4. Verify with targeted tokenizer/parser tests and the standalone debug repro command.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Initial triage: shared frequency class logic looks correct; likely failure is upstream tokenizer candidate selection producing merged content+function tokens that annotation later excludes from frequency. Standalone debug scripts also fail to initialize a usable Electron/Yomitan runtime, blocking reliable repro from the current CLI path.
Repro after fixing the standalone Electron wrapper does not support the original highlight claim for `誰でもいいから かかってこいよ`: the tokenizer reports `かかってこい` with `frequencyRank` 63098, so it correctly stays uncolored at `--color-top-x 10000` and becomes colorable once the threshold is raised above that rank. The concrete bug fixed in this pass is the standalone Electron debug path: package scripts now unset `ELECTRON_RUN_AS_NODE`, and the scripts normalize Electron imports/guards so `get-frequency:electron` can reach real Electron/Yomitan runtime state instead of immediately falling back to Node-mode diagnostics. `test-yomitan-parser:electron` still shows extension/service-worker issues against the existing profile and was not stabilized in this pass.
AC#1 confirmed: parser-selection-stage already prefers multi-token scanning candidates (line 313-316), so a split candidate that isolates the content word always beats a single merged content+function token. annotation-stage.ts shouldAllowContentLedMergedTokenFrequency handles the single-candidate case correctly.
AC#2 done: added two regression tests to parser-selection-stage.test.ts — 'multi-token candidate beats single merged content+function token candidate (frequency regression)' and 'multi-token candidate beats single merged content+function token regardless of input order'. Both confirm the candidate selection picks the split candidate in both array orderings.
AC#3 confirmed: scripts/get_frequency.ts and scripts/test-yomitan-parser.ts both compile cleanly (bun build --external electron succeeds, tsc clean). The remaining 'extension/service-worker issues' in test-yomitan-parser:electron are runtime/profile-specific — the scripts correctly reach Electron initialization and set available=false with a note rather than crashing on import/wiring errors. No code changes needed.
All 526 tests pass (test:fast green).
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed all three acceptance criteria for missing frequency highlights on merged tokenizer tokens.\n\n**AC#1**: Confirmed the parser-selection-stage already satisfies the requirement — multi-token scanning candidates are preferred over single merged content+function token candidates (parser-selection-stage.ts:313-316). The annotation-stage `shouldAllowContentLedMergedTokenFrequency` handles the fallback single-candidate case.\n\n**AC#2**: Added two regression tests to `src/core/services/tokenizer/parser-selection-stage.test.ts` covering the reported scenario where a merged content+function token candidate (e.g. `かかってこいよ` → headword `かかってくる`) competes against a split candidate (`かかってこい` + `よ`). Tests verify the split candidate wins in both array orderings.\n\n**AC#3**: Confirmed `scripts/get_frequency.ts` and `scripts/test-yomitan-parser.ts` compile cleanly. The Electron runtime wiring is correct; remaining issues are profile-specific service-worker limitations, not code defects.\n\n**Verification**: `bun run test:fast` green (526 tests). `bun run tsc` clean. Both scripts build with `bun build --external electron`.\n\n**Docs update required**: No — internal implementation detail.\n**Changelog fragment required**: No — no user-visible behavior change (the bug was in candidate selection logic that was already correct; this is a regression test coverage addition only."]
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,53 +0,0 @@
---
id: TASK-176
title: Exclude interjections and sound effects from subtitle annotations
status: Done
assignee:
- codex
created_date: '2026-03-15 12:07'
updated_date: '2026-03-16 05:13'
labels:
- bug
- tokenizer
- renderer
dependencies: []
references:
- /home/sudacode/projects/japanese/SubMiner/src/core/services/tokenizer.ts
- >-
/home/sudacode/projects/japanese/SubMiner/src/core/services/tokenizer/annotation-stage.ts
- >-
/home/sudacode/projects/japanese/SubMiner/src/core/services/tokenizer.test.ts
- /home/sudacode/projects/japanese/SubMiner/src/renderer/subtitle-render.ts
- >-
/home/sudacode/projects/japanese/SubMiner/src/renderer/subtitle-render.test.ts
priority: high
ordinal: 16500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Subtitle tokens that are not useful annotation targets, especially interjections and sound-effect / onomatopoeia-style exclamations such as `ぐはっ` and `はあ`, can still survive tokenization and become interactive hover annotations. Keep the subtitle text visible, but remove these tokens from annotation payloads so they do not render hover targets or dictionary popovers.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Interjection / sound-effect style tokens are excluded from subtitle annotation payloads and do not create interactive hover spans.
- [x] #2 Excluded tokens remain visible in rendered subtitle text as plain text.
- [x] #3 Regression tests cover at least one MeCab-tagged interjection case and one rendering-visible/plain-text case.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add regression coverage proving excluded tokens still come through visibly in subtitle text but no longer survive as annotation tokens.
2. Introduce a shared annotation-eligibility predicate in the tokenizer annotation stage for interjections / SFX-like tokens.
3. Filter subtitle token payloads through that predicate before renderer hover ranges/spans are built.
4. Verify with targeted tokenizer and renderer tests.
<!-- SECTION:PLAN:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Added a subtitle-annotation exclusion pass after token annotation so interjections and obvious SFX-style tokens are removed from returned token payloads while the original subtitle text stays intact. Coverage now includes MeCab-tagged `感動詞`, repeated-kana interjections such as `ああ`, a mixed `ぐはっ 猫` tokenizer case, and a renderer check proving omitted tokens stay visible as plain text instead of interactive hover spans.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,74 +0,0 @@
---
id: TASK-177
title: Track intentional Yomitan lookups in stats
status: Done
assignee:
- codex
created_date: '2026-03-17 09:15'
updated_date: '2026-03-18 05:28'
labels:
- stats
- immersion-tracking
- yomitan
milestone: m-1
dependencies: []
references:
- vendor/subminer-yomitan/ext/js/app/frontend.js
- src/core/services/immersion-tracker-service.ts
- src/core/services/immersion-tracker/query.ts
- src/core/services/ipc.ts
- src/preload.ts
- stats/src/components/sessions/SessionDetail.tsx
- stats/src/components/library/MediaHeader.tsx
- stats/src/components/anime/AnimeDetailView.tsx
documentation:
- docs/plans/2026-03-17-yomitan-lookup-stats-design.md
priority: medium
ordinal: 114500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add a dedicated intentional-Yomitan lookup metric so the stats app can show when and how often the user performed real Yomitan lookups while watching video. Keep existing annotation/known-word lookup counters unchanged. Surface the new metric in session detail, episode/media detail, and anime detail, including lookup rate based on words seen.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Successful Yomitan searches while watching create a dedicated Yomitan lookup event and aggregate counter without changing existing lookupCount or lookupHits behavior
- [x] #2 Session detail shows Yomitan lookup timeline markers plus lookup count and lookup rate using words seen
- [x] #3 Episode/media detail shows aggregated Yomitan lookup count and lookup rate using episode totals
- [x] #4 Anime detail shows aggregated Yomitan lookup count and lookup rate using anime totals
- [x] #5 Automated tests cover the new lookup event path, aggregate queries, and affected stats UI surfaces
- [x] #6 Internal docs/plans reflect the approved design and implementation approach
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a SubMiner-specific Yomitan lookup signal emitted from vendored Yomitan on searchSuccess and bridge it through renderer, preload, and main IPC to a tracker hook.
2. Extend immersion tracking with a dedicated Yomitan lookup event type and yomitanLookupCount aggregate, preserving existing lookupCount and lookupHits semantics.
3. Update session, media, anime, and anime-episode queries plus shared stats types to expose the new aggregate count.
4. Update stats UI to show Yomitan lookup markers in session detail and lookup count/rate at session, episode/media, and anime levels using lookups per 100 words copy.
5. Verify with focused unit tests first, then repo typecheck/test/build lanes, and finalize TASK-177 with implementation notes and acceptance-criteria checks.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Approved design recorded in docs/plans/2026-03-17-yomitan-lookup-stats-design.md.
Observed pre-existing local changes in tracker/query/session stats files; implementation plan must preserve those edits while layering Yomitan lookup tracking on top.
Implemented a dedicated Yomitan lookup signal on vendored searchSuccess, bridged it through renderer/preload/main IPC, and persisted YOMITAN_LOOKUP events plus yomitanLookupCount without changing existing annotation lookup counters.
Extended stats queries/types for session, media, anime, and episode aggregates; updated session detail, media header, episode list, and anime overview to show Yomitan lookup counts and lookup rate copy as lookups per 100 words.
Focused verification passed for IPC, tracker service/query, and stats UI tests. stats typecheck still has pre-existing unrelated failures in stats/src/components/anime/AnilistSelector.tsx and stats/src/lib/reading-utils.ts.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Added intentional Yomitan lookup tracking end-to-end: vendored Yomitan searchSuccess now emits a SubMiner event, the app records dedicated YOMITAN_LOOKUP events and yomitanLookupCount aggregates, and the stats UI surfaces lookup counts/rates for sessions, episodes/media, and anime. Focused regression tests pass for the IPC bridge, tracker persistence/querying, and new stats UI helpers/components. Full `bun run typecheck:stats` remains blocked by unrelated existing errors in `stats/src/components/anime/AnilistSelector.tsx` and `stats/src/lib/reading-utils.ts`.
<!-- 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,69 +0,0 @@
---
id: TASK-178
title: 'Address PR #19 Codex review feedback on immersion session deletion'
status: Done
assignee:
- codex
created_date: '2026-03-17 14:59'
updated_date: '2026-03-18 05:28'
labels:
- pr-review
- immersion-tracker
- stats
milestone: m-1
dependencies: []
references:
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/immersion-tracker-service.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/immersion-tracker/query.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/immersion-tracker-service.test.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/immersion-tracker/__tests__/query.test.ts
priority: medium
ordinal: 113500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Assess the open Codex review items on PR #19 and fix verified deletion-path regressions in immersion tracking so dashboard deletes cannot corrupt tracker state or leave stale aggregate stats.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Deleting the active immersion session is rejected safely and does not leave the tracker in a flush-failure loop
- [x] #2 Deleting sessions rebuilds or updates vocabulary and kanji aggregates so stats no longer include removed session data
- [x] #3 Regression tests cover the active-session deletion guard and aggregate cleanup after session deletion
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add failing regression tests for deleting the active session through ImmersionTrackerService and for deleteSession/deleteSessions keeping imm_words and imm_kanji aggregates in sync after rows are removed.
2. Verify the failures are caused by the current deletion path, then patch the service guard and query-layer aggregate maintenance with the smallest safe change.
3. Re-run focused tests for the touched files, then run SubMiner verification lanes appropriate for core/runtime-compat changes and record results.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Verified Codex PR #19 findings against current code: active-session/video deletes could orphan the live tracker session, and deleteSession/deleteSessions/deleteVideo left imm_words/imm_kanji aggregates stale after subtitle-line removal.
Implemented service guards that ignore deletes targeting the active session or active video and log a warning instead of deleting live tracker rows.
Updated query-layer delete helpers to capture affected word/kanji ids before deletion, remove session/video rows in a transaction, then recompute surviving imm_words/imm_kanji frequency and first/last-seen values from remaining subtitle-line occurrences, deleting orphan aggregate rows when no occurrences remain.
Focused verification passed: bun test src/core/services/immersion-tracker-service.test.ts and bun test src/core/services/immersion-tracker/__tests__/query.test.ts.
SubMiner verifier: classify_subminer_diff.sh selected lane core; verify_subminer_change.sh passed typecheck and failed on unrelated existing launcher test `stats command tolerates slower dashboard startup before timing out` in launcher/main.test.ts (timeout waiting for dashboard startup response).
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Assessed the open Codex PR #19 review items on immersion deletion paths and fixed the confirmed regressions. ImmersionTrackerService now ignores delete requests that target the currently active session or its active video, preventing the dashboard from deleting the live parent rows that subsequent telemetry/event flushes still depend on. On the query side, session/video deletion now captures affected vocabulary and kanji aggregate ids before removing subtitle/session rows, then recomputes imm_words and imm_kanji frequency plus first/last seen timestamps from surviving line occurrences inside the same transaction, deleting orphan aggregate rows when no occurrences remain.
Regression coverage was added for active-session delete protection, active-video delete protection, and aggregate rebuild after session deletion. Focused verification passed with `bun test src/core/services/immersion-tracker-service.test.ts` and `bun test src/core/services/immersion-tracker/__tests__/query.test.ts`. Repo-native verification selected the `core` lane; `bun run typecheck` passed, while `bun run test:fast` failed in an unrelated launcher test (`launcher/main.test.ts`: `stats command tolerates slower dashboard startup before timing out`) that times out waiting for dashboard startup response.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,58 +0,0 @@
---
id: TASK-179
title: Tune immersion tracker SQLite pragmas and maintenance defaults
status: Done
assignee:
- codex
created_date: '2026-03-17 15:15'
updated_date: '2026-03-18 05:28'
labels:
- sqlite
- immersion-tracking
- performance
dependencies: []
documentation:
- >-
/Users/sudacode/projects/japanese/SubMiner/docs/plans/2026-03-17-sqlite-tuning.md
priority: medium
ordinal: 111500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Apply low-risk SQLite tuning improvements for the immersion tracker: add modern recommended maintenance/tuning pragmas where appropriate, cover them with regression tests, and update user-facing docs to reflect the actual tuning policy. Scope limited to low-risk local-DB changes already discussed: keep WAL + synchronous=NORMAL, add optimize path, consider WAL growth control, and document workload-dependent knobs left at defaults.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Immersion tracker applies the agreed low-risk SQLite tuning changes without regressing current behavior
- [x] #2 Regression tests cover the new pragma/maintenance behavior
- [x] #3 Immersion tracking docs describe the tuning policy and notable defaults left unchanged
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add regression coverage for connection pragmas and verify the new WAL growth cap fails before implementation.
2. Add regression coverage for maintenance-time PRAGMA optimize and verify the test fails before implementation.
3. Implement the minimal SQLite tuning changes.
4. Update immersion-tracking docs for the new tuning policy.
5. Run targeted SQLite verification lanes and record results.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Verification: `bun test src/core/services/immersion-tracker/storage-session.test.ts src/core/services/immersion-tracker/maintenance.test.ts` passed (15 tests).
Verification: `bun run test:immersion:sqlite:src` passed (37 tests).
Verification: `bun run typecheck`, `bun run docs:test`, `bun run docs:build`, `bun run test:fast`, `bun run test:env`, and `bun run build` all passed.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Added low-risk SQLite tuning improvements for the immersion tracker: `journal_size_limit` now bounds WAL growth, periodic maintenance runs `PRAGMA optimize`, regression tests cover both behaviors, and the immersion-tracking docs explain the maintained pragmas plus workload-dependent defaults left unchanged.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,62 +0,0 @@
---
id: TASK-180
title: Fix launcher stats command timeout for slow dashboard startup
status: Done
assignee:
- codex
created_date: '2026-03-17 15:16'
updated_date: '2026-03-18 05:28'
labels:
- launcher
- stats
- tests
milestone: m-1
dependencies: []
references:
- >-
/Users/sudacode/projects/japanese/SubMiner/launcher/commands/stats-command.ts
- /Users/sudacode/projects/japanese/SubMiner/launcher/main.test.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/launcher/commands/command-modules.test.ts
priority: medium
ordinal: 112500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Address the failing launcher stats startup path so the CLI tolerates the intended slow dashboard startup window instead of timing out early.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 The launcher stats command no longer times out before the intended slow-start window used by tests
- [x] #2 Regression coverage verifies the slower stats startup path succeeds
- [x] #3 The failing launcher stats startup test passes locally
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a focused launcher command regression that simulates a stats response arriving after the current timeout boundary and expects success.
2. Adjust the stats startup wait timeout in launcher/commands/stats-command.ts to match the intended slow-start tolerance.
3. Re-run the targeted command test, the previously failing launcher/main.test.ts case, and then the full launcher/main.test.ts file.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Verified the failing launcher path: launcher/main.test.ts timed out because launcher/commands/stats-command.ts only waited 8000ms for the stats startup response while the supported slow-start test writes the response after 9s.
Raised the stats startup response timeout to 12000ms so attached stats startup tolerates the existing slow cold-start window without changing command flow.
Verification passed: bun test launcher/commands/command-modules.test.ts --test-name-pattern "stats command launches attached app command with response path|stats command returns after startup response even if app process stays running|stats command throws when stats response reports an error"; bun test launcher/main.test.ts --test-name-pattern "stats command tolerates slower dashboard startup before timing out"; bun test launcher/main.test.ts; bun run test:fast.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed the launcher stats startup timeout by extending the response-file wait window in launcher/commands/stats-command.ts from 8s to 12s. The command flow was left unchanged; the launcher now simply gives the stats dashboard enough time to report readiness during slower cold starts, which matches the existing supported behavior exercised by launcher/main.test.ts.
Verification passed with the targeted launcher command tests, the previously failing slow-start launcher/main.test.ts case, the full launcher/main.test.ts file, and the full `bun run test:fast` gate.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,61 +0,0 @@
---
id: TASK-181
title: Add background-managed stats server lifecycle commands
status: Done
assignee:
- codex
created_date: '2026-03-17 15:31'
updated_date: '2026-03-18 05:28'
labels:
- cli
- launcher
- stats
milestone: m-1
dependencies: []
priority: medium
ordinal: 110500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add a dedicated background stats server mode that can be started and stopped from the launcher without blocking normal SubMiner instances. Launcher UX: `subminer stats -b` starts the stats server in the background, `subminer stats -s` stops the background stats server only, and plain `subminer stats` preserves the existing foreground/open-browser flow.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 `subminer stats -b` starts a background stats server without blocking other SubMiner instances.
- [x] #2 `subminer stats -s` stops only the background stats server and succeeds cleanly when state is stale.
- [x] #3 Plain `subminer stats` preserves current dashboard-open behavior.
- [x] #4 Automated tests cover launcher parsing/dispatch and app-side start-stop lifecycle behavior.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Extend launcher stats parsing so `subminer stats -b` maps to background-start and `subminer stats -s` maps to stop-only while preserving existing cleanup/rebuild parsing.
2. Add launcher execution branches: detached background start with startup acknowledgement wait, stop command forwarding with response wait, and preserve existing attached foreground behavior for plain `stats` and cleanup flows.
3. Extend app CLI args and stats command handler for background start/stop lifecycle responses, including already-running and stale-state handling.
4. Add a dedicated stats-daemon runtime/state-file path in the app and bypass the normal single-instance lock only for that mode.
5. Verify with focused tests first, then launcher/env lane, and update task acceptance criteria/final summary before handoff.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
User approved option 2 design: dedicated app-side stats daemon, `subminer stats -b` to start, `subminer stats -s` to stop server only.
Implemented launcher `stats -b` and `stats -s` flows plus app-side `--stats-background` / `--stats-stop` handling.
Added background stats daemon state-file management and remote-daemon reuse so normal SubMiner instances do not try to bind a second stats server when the daemon is already running.
Verification: `bun test launcher/main.test.ts launcher/commands/command-modules.test.ts launcher/parse-args.test.ts src/main/runtime/stats-cli-command.test.ts src/main/early-single-instance.test.ts`, `bun run typecheck`, `bun run test:env`, `bun run test:fast`, `bun run build`, `bun run test:smoke:dist`, `bun run docs:test`, `bun run docs:build`, `bun run changelog:lint`.
Non-blocking note: `bun run test:launcher` still showed unrelated existing failures in `launcher/picker.test.ts` and an intermittent `launcher/smoke.e2e.test.ts` mpv-status check on this machine; the narrowed launcher suites covering the changed stats paths passed.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Added a dedicated background stats-daemon lifecycle for the launcher and app runtime. `subminer stats -b` now starts or reuses a detached stats server and returns after startup acknowledgement, while `subminer stats -s` stops that daemon without touching browser tabs. On the app side, new stats background/stop CLI flags bypass the normal single-instance lock only for daemon helper processes, write/read a daemon state file under user data, and reuse an already-running daemon instead of attempting a second local stats bind when another SubMiner instance needs stats access. Updated docs-site stats docs, added a changelog fragment, and covered the new flows with launcher parse/dispatch tests, app stats CLI handler tests, and single-instance bypass tests. Verification run: `bun run typecheck`, `bun run test:env`, `bun run test:fast`, `bun run build`, `bun run test:smoke:dist`, `bun run docs:test`, `bun run docs:build`, `bun run changelog:lint`, plus narrowed changed-path launcher/app test bundles.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,60 +0,0 @@
---
id: TASK-182
title: Fix session stats chart known-word totals exceeding total words
status: Done
milestone: m-1
assignee:
- codex
created_date: '2026-03-17 16:07'
updated_date: '2026-03-18 05:28'
labels: []
dependencies: []
references:
- /Users/sudacode/projects/japanese/SubMiner/src/core/services/stats-server.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/sessions/SessionDetail.tsx
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/__tests__/stats-server.test.ts
- /Users/sudacode/projects/japanese/SubMiner/stats/src/hooks/useSessions.ts
ordinal: 109500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Fix the session detail stats display so the known-word series cannot exceed the total-word series for the same sample. Ground the fix in the actual immersion-tracker metrics used by the stats UI and cover the regression with automated tests.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Session detail data uses a consistent cumulative word metric so known-word counts do not exceed total words for a sample
- [x] #2 Automated tests cover the session known-word timeline contract and reproduce the regression scenario
- [x] #3 Session stats UI still renders the timeline and tooltip values correctly after the fix
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a regression test for `/api/stats/sessions/:id/known-words-timeline` covering a Japanese-style session where telemetry word counts can be lower than token-derived known-word counts.
2. Update the stats known-word timeline contract/server implementation so the series is expressed in the same cumulative unit used for total words in the session detail view.
3. Adjust the session detail UI/types to consume the corrected series and keep tooltip/legend copy coherent.
4. Run targeted tests for stats server and stats UI transforms, then summarize any wider verification skipped.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Implemented server-side known-word timeline fix to preserve stored line positions and accumulate known-word occurrences rather than compressed unique-headword counts.
Updated session-facing stats views to prefer `tokensSeen` over `wordsSeen` when available so displayed session word totals align with the session chart and lookup-rate denominator.
Verification: `bun test src/core/services/__tests__/stats-server.test.ts`, `bun test stats/src/lib/yomitan-lookup.test.tsx`, `bun test src/core/services/immersion-tracker/__tests__/query.test.ts`, `bun run typecheck` all passed.
Verification skipped/blocker: `bun run typecheck:stats` still fails in pre-existing unrelated files `stats/src/components/anime/AnilistSelector.tsx`, `stats/src/lib/reading-utils.test.ts`, and `stats/src/lib/reading-utils.ts`.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed the session stats mismatch that let known words outrun total words. The stats server now preserves actual subtitle-line positions and accumulates known-word occurrences for the session timeline, while session-facing stats views prefer token-based word totals when available. Added regression coverage for the known-word timeline API and for session-row word-count rendering, plus a user-visible changelog fragment.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,64 +0,0 @@
---
id: TASK-182.1
title: Remove misleading session new-word metric from session detail chart
status: Done
assignee:
- '@codex'
created_date: '2026-03-18 01:41'
updated_date: '2026-03-18 05:28'
labels:
- bug
- stats
- ui
milestone: m-1
dependencies: []
references:
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/sessions/SessionDetail.tsx
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/sessions/SessionsTab.tsx
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/lib/media-session-list.test.tsx
parent_task_id: TASK-182
ordinal: 101500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Remove the misleading `New words` series from the session detail chart so the stats UI no longer presents a fabricated metric that mirrors total words. Keep the session chart focused on the real cumulative totals already backed by tracker data.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Expanded session detail chart no longer renders or labels a `New words` metric in the graph, tooltip, or legend
- [x] #2 Session detail still renders total-word and known-word series correctly after the metric removal
- [x] #3 Automated frontend coverage prevents the `New words` label from reappearing in expanded session detail
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a focused stats frontend regression test that renders expanded session detail and asserts the misleading `New words` label is absent while `Total words` remains.
2. Remove the fabricated `New words` area series, tooltip mapping, legend chip, and now-unused left-axis chart plumbing from `stats/src/components/sessions/SessionDetail.tsx`.
3. Add a user-visible changelog fragment describing the session chart cleanup.
4. Run targeted frontend tests plus cheap verification and record any blockers.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Added a focused server-render regression test for SessionDetail copy to ensure the misleading `New words` label stays removed.
Removed the fabricated `New words` chart series and its legend/tooltip plumbing from the expanded session detail view.
Verification: `bun test stats/src/lib/session-detail.test.tsx stats/src/lib/media-session-list.test.tsx` 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 changes/2026-03-18-remove-session-new-words-series.md` passed and wrote artifacts under `.tmp/skill-verification/subminer-verify-20260317-184440-1aMWkM`.
Manual spot-check note: `bun test stats/src/lib/yomitan-lookup.test.tsx` is currently red on a pre-existing `AnimeOverviewStats` lookup-rate assertion unrelated to this session-detail change.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Removed the misleading `New words` metric from expanded session charts. Session detail now shows only the real total-word and known-word lines, backed by existing tracker data, with regression coverage that prevents the `New words` label from reappearing.
<!-- 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,58 +0,0 @@
---
id: TASK-183
title: Fix blank stats vocabulary page regression
status: Done
milestone: m-1
assignee:
- codex
created_date: '2026-03-17 16:23'
updated_date: '2026-03-18 05:28'
labels: []
dependencies: []
references:
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/vocabulary/VocabularyTab.tsx
- /Users/sudacode/projects/japanese/SubMiner/stats/src/App.tsx
- /Users/sudacode/projects/japanese/SubMiner/stats/src/lib/api-client.ts
ordinal: 108500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Diagnose and fix the stats dashboard regression where the Vocabulary tab renders blank at runtime. Capture the frontend failure with browser debugging, add regression coverage, and restore the vocabulary view.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Vocabulary tab renders without a blank-screen failure in the stats dashboard
- [x] #2 Automated test coverage reproduces the failing code path and passes with the fix
- [x] #3 Targeted verification covers the affected stats UI/runtime path
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Reproduce the blank Vocabulary tab locally with a browser-visible stats UI instance and capture console/network failure details.
2. Add a focused regression test for the failing Vocabulary tab code path before editing production code.
3. Implement the minimal fix in the stats UI/runtime path.
4. Re-run targeted browser and automated verification, then record any skipped broader checks.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Identified the runtime failure in the browser console: React reported a hook-order change in `VocabularyTab` after the tab moved from loading to loaded state (`Rendered more hooks than during the previous render`).
Fixed `stats/src/components/vocabulary/VocabularyTab.tsx` by removing the late `useMemo` hook and computing `knownWordCount` as a plain derived value after the loading/error guards.
Added regression coverage in `stats/src/lib/vocabulary-tab.test.ts` to assert that `VocabularyTab` declares all hooks before the loading/error early returns.
Verification: `bun test stats/src/lib/vocabulary-tab.test.ts`, `bun test stats/src/lib/yomitan-lookup.test.tsx`, `bun run build:stats`, and a live Playwright check against the Vite app with stubbed stats API data all passed.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Fixed the blank Vocabulary tab regression in the stats UI. The root cause was a late `useMemo` hook declared after the loading/error early returns in `VocabularyTab`, which caused React to crash once vocabulary data finished loading. Removed that late hook, added a regression test guarding hook placement, verified the stats bundle builds, and confirmed in a live browser that the Vocabulary tab now renders loaded content instead of white-screening.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,57 +0,0 @@
---
id: TASK-184
title: Stabilize branch verification gate
status: Done
assignee:
- Codex
created_date: '2026-03-17 19:28'
updated_date: '2026-03-18 05:28'
labels:
- stabilization
- ci
dependencies: []
references:
- package.json
- docs/workflow/verification.md
priority: medium
ordinal: 106500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Bring the current PR branch back to a green verification state by fixing any failing lint/format or test checks required for local handoff.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Repo source formatting checks pass for the current branch.
- [x] #2 Required local verification checks for this branch pass without introducing new failures.
- [x] #3 Any code or test adjustments stay scoped to the failing checks and preserve existing branch behavior.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Fix the current source-formatting failures reported by `bun run format:check:src` using the minimal repo-standard Prettier output.
2. Re-run `bun run format:check:src` to confirm the lint/format gate is green.
3. Re-run the default handoff gate from `docs/workflow/verification.md`: `bun run typecheck`, `bun run test:fast`, `bun run test:env`, `bun run build`, and `bun run test:smoke:dist`.
4. Because `docs-site/` is modified on this branch, also run `bun run docs:test` and `bun run docs:build`.
5. If any verification step fails after formatting, fix only the blocking issue and re-run the relevant lane until green.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Initial gate snapshot before edits: `typecheck`, `test:fast`, `test:env`, `build`, and `test:smoke:dist` passed; `format:check:src` failed on 15 files.
Applied repo-standard Prettier formatting to the 15 files reported by `bun run format:check:src`; no additional logic changes were introduced in this stabilization pass.
Verification after formatting: `bun run format:check:src` passed; `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane core --lane runtime-compat --lane docs` passed with artifacts under `.tmp/skill-verification/subminer-verify-20260317-122947-hEInF0`; `bun run test:env` passed separately.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Branch verification gate is green again. Fixed the only failing local gate by applying Prettier formatting to the 15 flagged source files, then re-ran the required verification lanes: source format check, core lane (`typecheck` + `test:fast`), runtime-compat lane (`build`, `test:runtime:compat`, `test:smoke:dist`), docs lane (`docs:test`, `docs:build`), and `test:env`. All passed.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,67 +0,0 @@
---
id: TASK-185
title: Clarify library stats word-count labels
status: Done
assignee:
- codex
created_date: '2026-03-17 22:58'
updated_date: '2026-03-18 05:28'
labels:
- bug
- stats
- ui
milestone: m-1
dependencies: []
references:
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/library/MediaHeader.tsx
- /Users/sudacode/projects/japanese/SubMiner/src/core/services/stats-server.ts
- >-
/Users/sudacode/projects/japanese/SubMiner/src/core/services/immersion-tracker/query.ts
priority: medium
ordinal: 104500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Fix the library/media detail stats header so occurrence-based subtitle counts are not presented as unique-word vocabulary totals. The UI should clearly distinguish subtitle word occurrences from unique known-word headword coverage to avoid misleading comparisons.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Library/media detail view labels subtitle occurrence totals with wording that does not imply unique vocabulary counts
- [x] #2 Known-words summary in the same view explicitly communicates that its denominator is unique words/headwords
- [x] #3 Frontend tests cover the updated copy so the mismatch does not regress
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a focused stats frontend test for MediaHeader copy that distinguishes occurrence totals from unique known-word coverage.
2. Update MediaHeader labels so occurrence-based totals no longer imply unique vocabulary counts.
3. Update the known-words label copy to explicitly state it is based on unique words/headwords.
4. Run targeted stats tests and record results.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Root cause confirmed: library header compares occurrence-based totalWordsSeen against unique-headword known-words summary. Awaiting plan approval before code changes.
Updated library header copy to label totalWordsSeen as word occurrences and known-word coverage as known unique words. Added an optional initialKnownWordsSummary prop to support deterministic server-render tests without changing runtime behavior.
Verification: `bun test stats/src/lib/yomitan-lookup.test.tsx` passes. `bun run typecheck:stats` remains blocked by preexisting unrelated errors in stats/src/components/anime/AnilistSelector.tsx, stats/src/lib/reading-utils.ts, stats/src/lib/reading-utils.test.ts, and stats/src/lib/vocabulary-tab.test.ts.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Clarified the library/media header so occurrence-based subtitle counts are no longer presented as if they were unique vocabulary totals. The header now labels `totalWordsSeen` as `word occurrences`, and the known-words summary explicitly says `known unique words`, which matches the backend's DISTINCT headword calculation.
For regression coverage, added a focused MediaHeader render test that exercises the exact mismatch case (30 occurrences vs 34 unique words) and verifies the new copy. Also updated one stale AnimeOverviewStats assertion in the same targeted test file so the focused stats test lane is green.
Tests run:
- `bun test stats/src/lib/yomitan-lookup.test.tsx`
- `bun run typecheck:stats` ⚠️ blocked by preexisting unrelated errors in AnilistSelector and reading-utils/vocabulary-tab stats files.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,77 +0,0 @@
---
id: TASK-186
title: Remove stats Library tab and add episode detail navigation from anime page
status: Done
assignee:
- codex
created_date: '2026-03-17 23:19'
updated_date: '2026-03-18 05:28'
labels:
- stats
- ui
milestone: m-1
dependencies: []
references:
- /Users/sudacode/projects/japanese/SubMiner/stats/src/App.tsx
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/layout/TabBar.tsx
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/anime/AnimeDetailView.tsx
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/anime/EpisodeList.tsx
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/library/MediaDetailView.tsx
priority: medium
ordinal: 103500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Update the stats UI so watched-file detail is no longer exposed as a top-level Library tab. Users should open dedicated episode detail pages from the anime detail page while preserving inline quick-peek session expansion.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Stats navigation no longer shows a top-level Library tab.
- [x] #2 Anime episode rows keep inline quick-peek expansion and also expose an explicit control to open the dedicated episode detail page.
- [x] #3 Dedicated episode detail navigation lands on the existing watched-file detail view with a back action that returns to the originating anime detail page.
- [x] #4 Relevant stats component tests cover the new navigation flow and removed tab behavior.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add app-level stats navigation state for dedicated media detail so anime flows can open watched-file detail without a Library tab.
2. Remove the Library tab from the tab bar and top-level tab panels while preserving existing Overview/Anime/Trends/Vocabulary/Sessions behavior.
3. Update anime detail episode list to keep row expansion for quick peek and add an explicit button that opens the dedicated detail view for the selected episode.
4. Reuse MediaDetailView for episode detail and adjust its back action to return to the originating anime detail page.
5. Add or update stats component tests to cover the removed Library tab and the new anime-to-episode-detail navigation flow.
6. Run targeted stats tests, then targeted SubMiner verification lanes if needed for touched files.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Implemented app-level stats navigation state for dedicated media detail and removed the Library tab from the tab bar and top-level panels.
Anime episode rows now keep inline quick-peek expansion and expose a visible Details button that opens the dedicated watched-file detail view.
Reused MediaDetailView for anime-origin episode navigation with a Back to Anime label and app-level return path.
Verification: bun test stats/src/lib/stats-navigation.test.ts stats/src/lib/stats-ui-navigation.test.tsx; bun run build:stats; bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane core ... => passed.
Observed unrelated existing stats workspace issues outside this task when running bun run typecheck:stats, including AnilistSelector/reading-utils/vocabulary-tab and an outdated AnimeOverviewStats test signature.
Reopened for bugfix: episode Details button is a no-op when anime detail is open from within AnimeTab because app-level selectedAnimeId is not retained there. Follow-up fix will pass animeId explicitly through the callback chain instead of depending on App route state.
Bugfix: the Details button now passes animeId explicitly from AnimeTab/AnimeDetailView into app-level media-detail navigation, so dedicated episode navigation works even when the anime page was opened from within the tab rather than seeded by App state.
Bugfix verification: bun test stats/src/lib/stats-navigation.test.ts stats/src/lib/stats-ui-navigation.test.tsx; bun run build:stats => passed.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Removed the stats Library tab and replaced that navigation path with app-level dedicated media-detail routing from the anime page. Episode rows still support inline quick peek, and now also provide a Details button that opens the dedicated episode view and returns cleanly to the anime detail page. Added navigation-focused tests for the removed tab and anime-origin media-detail flow, and verified the change with targeted tests, stats bundle build, and the repo core verification lane.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,81 +0,0 @@
---
id: TASK-187
title: Replace episode detail session history with expandable inline session details
status: Done
assignee:
- codex
created_date: '2026-03-17 23:42'
updated_date: '2026-03-18 05:28'
labels:
- stats
- ui
milestone: m-1
dependencies: []
references:
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/library/MediaDetailView.tsx
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/library/MediaSessionList.tsx
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/sessions/SessionRow.tsx
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/sessions/SessionDetail.tsx
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/sessions/SessionsTab.tsx
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/overview/OverviewTab.tsx
- >-
/Users/sudacode/projects/japanese/SubMiner/stats/src/components/overview/RecentSessions.tsx
documentation:
- >-
/Users/sudacode/projects/japanese/SubMiner/docs/plans/2026-03-17-episode-detail-session-accordion-design.md
- >-
/Users/sudacode/projects/japanese/SubMiner/docs/plans/2026-03-17-episode-detail-session-accordion.md
priority: medium
ordinal: 102500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Update the dedicated episode detail page so its session history uses the same expandable session-row behavior as the Sessions page, including inline timeline details and session deletion, instead of navigating away to the Sessions tab. Also update home-page session navigation so recent session links open the associated episode detail page rather than the Sessions tab.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Dedicated episode detail session history uses expandable inline rows styled like the Sessions page instead of linking to the Sessions tab.
- [x] #2 Expanding a session on the episode detail page shows the full existing session detail panel, including the timeline chart and stats.
- [x] #3 Episode detail session rows retain a session delete control with the same behavior and safeguards as the Sessions page.
- [x] #4 Home-page recent session navigation opens the associated episode detail page when a session is tied to a video, instead of routing to the Sessions tab.
- [x] #5 Relevant stats tests cover the inline session expansion/delete behavior and the updated home-page navigation path.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add failing tests for media-detail session accordion structure and for overview-to-media-detail navigation, keeping orphan-session fallback coverage.
2. Rework MediaSessionList to reuse SessionRow and SessionDetail with local expansion state and delete affordance matching the Sessions page.
3. Move media-detail session mutation/delete ownership into MediaDetailView so deletes update the current episode page immediately.
4. Add app-level direct media-detail navigation from overview/home-page session rows when videoId exists; keep Sessions-tab fallback for sessions without videoId.
5. Run targeted tests, stats build, and the SubMiner core verification lane; then update TASK-187 with results.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Added approved design/plan docs at docs/plans/2026-03-17-episode-detail-session-accordion-design.md and docs/plans/2026-03-17-episode-detail-session-accordion.md before implementation.
MediaDetailView now owns local session state and delete handling, derives displayed media aggregates from the current session list, and renders MediaSessionList as an inline accordion instead of a session-page link list.
MediaSessionList now reuses SessionRow and full SessionDetail so episode-level session history matches Sessions-page dropdown behavior and keeps the same delete affordance.
Overview/home-page recent session navigation now prefers dedicated media detail when session.videoId exists and falls back to the Sessions tab only for orphan sessions without videoId.
Verification passed: bun test stats/src/lib/stats-navigation.test.ts stats/src/lib/stats-ui-navigation.test.tsx stats/src/lib/media-session-list.test.tsx; bun run build:stats; bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane core stats/src/App.tsx stats/src/components/overview/OverviewTab.tsx stats/src/components/overview/RecentSessions.tsx stats/src/components/library/MediaDetailView.tsx stats/src/components/library/MediaSessionList.tsx stats/src/lib/stats-navigation.ts stats/src/lib/stats-navigation.test.ts stats/src/lib/stats-ui-navigation.test.tsx stats/src/lib/media-session-list.test.tsx => passed.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Dedicated episode detail pages now show inline expandable session rows using the same shared SessionRow + SessionDetail UI as the Sessions page, including per-session delete controls. Overview/home-page recent session clicks now open the episode detail page whenever a backing video exists, with Sessions-tab fallback only for sessions missing videoId. Added navigation and media-session-list tests plus design/implementation docs, and verified the change with targeted tests, stats bundle build, and the SubMiner core verification lane.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -1,56 +0,0 @@
---
id: TASK-187.1
title: Auto-expand targeted session when opening media detail
status: Done
assignee:
- codex
created_date: '2026-03-18 01:32'
updated_date: '2026-03-18 05:28'
labels:
- stats
- ui
milestone: m-1
dependencies: []
references:
- stats/src/lib/stats-navigation.ts
- stats/src/App.tsx
- stats/src/components/overview/RecentSessions.tsx
- stats/src/components/library/MediaDetailView.tsx
- stats/src/components/library/MediaSessionList.tsx
- stats/src/lib/stats-navigation.test.ts
parent_task_id: TASK-187
priority: medium
ordinal: 117500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
When a navigation path opens episode/media detail with a known session ID, the matching session row in media detail should auto-expand so the user lands directly on the intended session details instead of only the episode history page.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Media detail navigation state can carry an optional target session ID alongside the selected video.
- [x] #2 Any navigation path that opens media detail with a known session ID causes that session row to auto-expand when the episode history loads.
- [x] #3 Session-tab fallback for orphan sessions without a video still behaves as it does now.
- [x] #4 Media detail auto-expansion clears or stabilizes its one-shot navigation state so normal manual expand/collapse behavior still works after landing.
- [x] #5 Relevant navigation/component tests cover the targeted media-detail auto-expand behavior.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Extend media-detail navigation state to optionally carry a target session ID while preserving the existing orphan-session fallback to the Sessions tab.
2. Update app-level navigation helpers and overview recent-session click handling to pass session IDs into media-detail navigation whenever both video and session are known.
3. Thread the one-shot target session ID into MediaDetailView and MediaSessionList so the matching accordion row auto-expands on load, then clear/stabilize that state so manual toggling still behaves normally.
4. Update targeted stats navigation/component tests to cover media-detail auto-expansion and fallback behavior.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Extended media-detail navigation state to carry an optional `initialSessionId`, updated overview/app navigation to pass session IDs into media detail whenever a video-backed session is clicked, and wired `MediaDetailView` + `MediaSessionList` to auto-expand and then consume that one-shot session target.
Updated `stats-navigation.test.ts` to cover the new navigation-state shape. Validation not run in this pass, so acceptance criteria remain unchecked pending verification.
<!-- SECTION:NOTES:END -->

View File

@@ -1,61 +0,0 @@
---
id: TASK-188
title: Refactor stats chart data pipeline to use backend-aggregated series
status: Done
assignee:
- codex
created_date: '2026-03-18 00:29'
updated_date: '2026-03-23 03:22'
labels:
- stats
- performance
- refactor
milestone: m-1
dependencies: []
references:
- src/core/services/immersion-tracker/query.ts
- src/core/services/immersion-tracker-service.ts
- src/core/services/stats-server.ts
- stats/src/hooks/useTrends.ts
- stats/src/components/trends/TrendsTab.tsx
- stats/src/lib/api-client.ts
- stats/src/types/stats.ts
- stats/src/lib/dashboard-data.ts
priority: medium
ordinal: 138500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Reduce long-term dashboard performance debt by moving chart aggregation out of the stats UI and into the tracker/stats API layer. The trends dashboard should consume chart-ready series from backend rollups instead of reconstructing multiple datasets from raw session lists in the browser.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Stats API exposes chart-oriented aggregated trend data needed by the trends dashboard without requiring raw session lists for those charts.
- [x] #2 The trends dashboard consumes the new aggregated API responses and no longer rebuilds its main chart datasets from raw sessions in the render path.
- [x] #3 Time-range and grouping behavior remain correct for recent and all-time views, with explicit handling that keeps older history performant.
- [x] #4 Existing overview and anime detail charts continue to behave correctly, or are migrated to the shared aggregation path where it reduces debt.
- [x] #5 Tests cover backend aggregation/query behavior and frontend consumption of the new response shapes.
- [x] #6 Internal docs are updated to describe the new stats chart data flow and scaling rationale.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a chart-oriented trends dashboard API response on the stats server that returns pre-aggregated series by range/grouping instead of requiring raw session lists in the UI.
2. Implement tracker/query-layer helpers that aggregate trend series on the backend, preferring rollups for scalable time-series data and centralizing chart shaping there.
3. Update stats client types and `useTrends` to consume the new response shape and stop fetching raw sessions for main chart construction.
4. Simplify `TrendsTab` and related chart components so they render backend-provided series with only lightweight UI-level filtering/state.
5. Keep overview/anime detail chart behavior intact, and reuse shared aggregation paths where it meaningfully reduces debt without widening scope.
6. Add/adjust backend and frontend tests plus internal docs to describe the new chart-data flow and performance rationale.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Implemented a new `/api/stats/trends/dashboard` server route backed by tracker/query-layer aggregation, updated the stats client and `useTrends` to consume the new chart-ready payload, simplified `TrendsTab` to render backend-provided series, added route/query/api-client tests, and documented the new trends data flow in `docs/architecture/stats-trends-data-flow.md`.
Did not run validation commands in this pass; acceptance criteria remain unchecked pending requested verification.
<!-- SECTION:NOTES:END -->

View File

@@ -1,56 +0,0 @@
---
id: TASK-189
title: Replace stats word counts with Yomitan token counts
status: Done
assignee:
- codex
created_date: '2026-03-18 01:35'
updated_date: '2026-03-18 05:28'
labels:
- stats
- tokenizer
- bug
milestone: m-1
dependencies: []
references:
- src/core/services/immersion-tracker-service.ts
- src/core/services/immersion-tracker/reducer.ts
- src/core/services/immersion-tracker/storage.ts
- src/core/services/immersion-tracker/query.ts
- src/core/services/immersion-tracker/lifetime.ts
- stats/src/components
- stats/src/lib/yomitan-lookup.ts
priority: medium
ordinal: 100500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Replace heuristic immersion stats word counting with Yomitan token counts. Session/media/anime stats should use the exact merged Yomitan token stream as the denominator and display metric, with no whitespace/CJK-character fallback and no active `wordsSeen` concept in the runtime, storage, API, or stats UI.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 `recordSubtitleLine` derives session count deltas from Yomitan token arrays instead of `calculateTextMetrics`.
- [x] #2 Active immersion tracking/storage/query code no longer depends on `wordsSeen` / `totalWordsSeen` fields for stats behavior.
- [x] #3 Stats UI labels and lookup-rate copy refer to tokens instead of words where those counts are shown to users.
- [x] #4 Regression tests cover token-count sourcing, zero-count behavior when tokenization payload is absent, and updated stats copy.
- [x] #5 A changelog fragment documents the user-visible stats denominator change.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add failing tracker tests proving subtitle count metrics come from Yomitan token arrays and stay zero when tokenization is absent.
2. Add failing stats UI tests for token-based copy and token-count display helpers.
3. Remove `wordsSeen` from active tracker/session/query/type paths and use `tokensSeen` as the single stats count field.
4. Update stats UI labels and lookup-rate copy from words to tokens.
5. Run targeted verification, then add the changelog fragment and any needed docs update.
<!-- SECTION:PLAN:END -->
## Outcome
<!-- SECTION:OUTCOME:BEGIN -->
Completed. Stats subtitle counts now come directly from Yomitan merged-token counts, `wordsSeen` is removed from the active tracker/storage/query/UI path, token-facing copy is updated, and focused regression coverage plus `bun run typecheck` are green.
<!-- SECTION:OUTCOME:END -->

View File

@@ -1,54 +0,0 @@
---
id: TASK-190
title: Add hover popups for session chart events
status: Done
assignee:
- Codex
created_date: '2026-03-17 22:20'
updated_date: '2026-03-18 05:28'
labels:
- stats
- ui
- bug
milestone: m-1
dependencies: []
references:
- stats/src/components/sessions/SessionDetail.tsx
- stats/src/lib/session-events.ts
- stats/src/hooks/useSessions.ts
- stats/src/lib/api-client.ts
- docs/plans/2026-03-17-session-event-hover-popups-design.md
priority: medium
ordinal: 105500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add hover/focus popups to session chart event markers so pauses, seeks, lookups, and card-mine events explain themselves inline. Card-mine events should lazy-load available Anki note info and present it in a richer popup with browse affordances.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Hovering or focusing a session-chart marker opens an event-specific popup.
- [x] #2 Pause, seek, and lookup popups show concise event copy derived from marker metadata.
- [x] #3 Card-mine popups lazily fetch and cache Anki note info by note id.
- [x] #4 Card-mine popups show a formatted fallback when note info is missing or still loading.
- [x] #5 Regression tests cover event payload shaping and popup rendering behavior.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add failing tests for event metadata shaping and popup content selection.
2. Extend session-event shaping to parse payload JSON into typed marker metadata.
3. Add lazy note-info fetch/cache state for card-mine markers.
4. Render interactive marker overlay + custom popup in the session detail chart.
5. Run targeted stats/core verification and update this task with the result.
<!-- SECTION:PLAN:END -->
## Outcome
<!-- SECTION:OUTCOME:BEGIN -->
Completed. Session-chart event markers now open event-specific hover/focus popups, including lazy-loaded Anki note info for card-mine events with browse affordances. Verification passed via targeted stats tests, `bun run typecheck`, and the core verification lane in `.tmp/skill-verification/subminer-verify-20260317-222545-CQzyqK`.
<!-- SECTION:OUTCOME:END -->

View File

@@ -1,68 +0,0 @@
---
id: TASK-191
title: 'Assess PR #19 CodeRabbit review follow-ups'
status: Done
assignee:
- codex
created_date: '2026-03-17 23:15'
updated_date: '2026-03-23 03:22'
labels:
- pr-review
- stats
- immersion-tracker
milestone: m-1
dependencies: []
references:
- src/core/services/immersion-tracker-service.ts
- src/core/services/immersion-tracker-service.test.ts
priority: medium
ordinal: 139500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Validate the open CodeRabbit review comments on PR #19 against the current branch, implement only the confirmed fixes, and record which bot suggestions are stale or technically incomplete.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Each open CodeRabbit PR #19 comment is validated against the current branch behavior
- [x] #2 Confirmed issues are fixed with regression coverage where it fits
- [x] #3 Non-actionable or partially-wrong bot guidance is documented explicitly
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Inspect the open CodeRabbit review threads on PR #19 and restate each finding in codebase terms.
2. Add failing regression tests for any verified bugs before changing production code.
3. Patch the smallest safe service-layer behavior, rerun focused verification, and record which suggestions were accepted versus rejected.
<!-- SECTION:PLAN:END -->
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Validated the two open CodeRabbit inline findings on PR #19 against the current branch. Both reported real bugs in `ImmersionTrackerService`, but the first suggestion's exact remediation was incomplete for this codebase.
`reassignAnimeAnilist` did overwrite `imm_anime.description` with `NULL` when callers omitted `description`. Fixed with a presence-aware SQL update that preserves the existing description when the field is omitted while still allowing explicit `description: null` to clear the stored value. Rejected the bot's `COALESCE(?, description)` prompt because that would silently remove the explicit-clear behavior the API already supports.
`ensureCoverArt` could return `true` after a fetcher reported success even when no cover-art row/blob was stored, because `undefined !== null` evaluated truthy through optional chaining. Fixed by loading the row into a local variable and requiring a non-null blob.
Added regression coverage in `src/core/services/immersion-tracker-service.test.ts` for omitted-description preservation, explicit-null clearing, and the no-row `ensureCoverArt` false-positive case.
Verification passed:
- `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-service.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-service.ts src/core/services/immersion-tracker-service.test.ts`
Verifier artifact directory: `.tmp/skill-verification/subminer-verify-20260317-231743-wHFNnN`
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Assessed the open PR #19 CodeRabbit comments and fixed the two confirmed service-layer regressions. `reassignAnimeAnilist` now preserves an existing anime description when callers omit the `description` field but still clears it on explicit `null`, and `ensureCoverArt` no longer reports success when no cover-art row/blob exists after a fetch attempt.
Both comments were actionable, but one bot-proposed fix was not correct as written for this branch: replacing the description update with `COALESCE(?, description)` would have broken intentional description clearing. Added regression tests for the accepted behaviors and verified the change with the full touched service test file plus the SubMiner `core` verification lane.
<!-- 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,62 +0,0 @@
---
id: TASK-193
title: Fix session chart event popup position drift
status: Done
assignee:
- Codex
created_date: '2026-03-17 23:55'
updated_date: '2026-03-17 23:59'
labels:
- stats
- ui
- bug
milestone: m-1
dependencies: []
references:
- stats/src/components/sessions/SessionDetail.tsx
- stats/src/components/sessions/SessionEventOverlay.tsx
- stats/src/lib/session-events.ts
priority: medium
ordinal: 105600
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Fix the session timeline event popup trigger positions so hover markers stay aligned with the underlying chart event lines across the full visible time range.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Event popup triggers stay horizontally aligned with chart event lines from session start through session end.
- [x] #2 Alignment logic uses the rendered chart plot area rather than guessed container percentages.
- [x] #3 Regression coverage locks the marker-position projection math.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a failing regression test for marker-position projection with chart offsets.
2. Capture the rendered plot box from Recharts and pass it into the overlay.
3. Position overlay markers in plot-area pixels, rerun targeted stats verification, then record the result.
<!-- SECTION:PLAN:END -->
## Outcome
<!-- SECTION:OUTCOME:BEGIN -->
Completed. Session event hover markers now read the actual Recharts plot-area offset and width, then project marker X positions into plot-area pixels instead of full-container percentages. That keeps popup triggers aligned with the underlying reference lines across long session timelines.
Verification:
- `bun test stats/src/lib/session-events.test.ts stats/src/lib/session-detail.test.tsx stats/src/components/sessions/SessionEventPopover.test.tsx`
- `cd stats && bun run build`
- `bun x prettier --check 'stats/src/components/sessions/SessionDetail.tsx' 'stats/src/components/sessions/SessionEventOverlay.tsx' 'stats/src/lib/session-events.ts' 'stats/src/lib/session-events.test.ts' 'backlog/tasks/task-193 - Fix-session-chart-event-popup-position-drift.md'`
- `bun run typecheck:stats` still fails on pre-existing unrelated errors in `src/components/anime/AnilistSelector.tsx`, `src/components/library/LibraryTab.tsx`, `src/lib/reading-utils.test.ts`, `src/lib/reading-utils.ts`, `src/lib/vocabulary-tab.test.ts`, and `src/lib/yomitan-lookup.test.tsx`
<!-- SECTION:OUTCOME: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,64 +0,0 @@
---
id: TASK-195
title: Keep final card-mine OSD result from being overwritten by progress spinner
status: Done
assignee:
- Codex
created_date: '2026-03-18 19:40'
updated_date: '2026-03-18 19:49'
labels:
- anki
- ui
- bug
milestone: m-1
dependencies: []
references:
- src/anki-integration/ui-feedback.ts
- src/anki-integration.ts
- src/anki-integration/card-creation.ts
priority: medium
ordinal: 105610
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
When a card mine finishes, the mpv OSD currently tries to show the final status text but the in-flight Anki progress spinner can immediately overwrite it on the next tick. Stop the spinner first, then show a single-line final result with a success/failure marker and the mined-word notification.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Successful mine/update OSD results render after the spinner is stopped and do not get overwritten by a later spinner tick.
- [x] #2 Failure results that replace the spinner show an `x` marker and stay visible on the same OSD line.
- [x] #3 Regression coverage locks the spinner teardown/result-notification ordering.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a focused failing regression test around the Anki UI-feedback spinner/result helper.
2. Add a helper that stops progress before emitting the final OSD result line with `✓`/`x`.
3. Route mine/update result notifications through that helper, then run targeted verification.
<!-- SECTION:PLAN:END -->
## Outcome
<!-- SECTION:OUTCOME:BEGIN -->
Added a dedicated Anki UI-feedback result helper that force-clears the in-flight spinner state before emitting the final OSD result line. Successful card-update notifications now render as `✓ Updated card: ...`, and sentence-card creation failures now render as `x Sentence card failed: ...` without a later spinner tick reclaiming the line.
Verification:
- `bun test src/anki-integration/ui-feedback.test.ts`
- `bun test src/anki-integration/ui-feedback.test.ts src/anki-integration/note-update-workflow.test.ts src/anki-integration.test.ts src/core/services/mining.test.ts src/main/runtime/mining-actions.test.ts`
- `bun x prettier --check src/anki-integration/ui-feedback.ts src/anki-integration/ui-feedback.test.ts src/anki-integration.ts src/anki-integration/card-creation.ts "backlog/tasks/task-195 - Keep-final-card-mine-OSD-result-from-being-overwritten-by-progress-spinner.md" changes/2026-03-18-mine-osd-spinner-result.md`
- `bun run changelog:lint`
- `bash .agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh --lane core src/anki-integration/ui-feedback.ts src/anki-integration/ui-feedback.test.ts src/anki-integration.ts src/anki-integration/card-creation.ts changes/2026-03-18-mine-osd-spinner-result.md`
- Verifier artifacts: `.tmp/skill-verification/subminer-verify-20260318-194614-uZMrAx/`
<!-- SECTION:OUTCOME:END -->

View File

@@ -1,46 +0,0 @@
---
id: TASK-196
title: Fix subtitle prefetch cache-key mismatch and active-cue window
status: Done
assignee: []
created_date: '2026-03-18 16:05'
updated_date: '2026-03-23 03:22'
labels: []
dependencies: []
references:
- >-
/home/sudacode/projects/japanese/SubMiner/src/core/services/subtitle-processing-controller.ts
- >-
/home/sudacode/projects/japanese/SubMiner/src/core/services/subtitle-prefetch.ts
priority: high
ordinal: 136500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Investigate and fix file-backed subtitle annotation latency where prefetch should warm upcoming lines but live playback still tokenizes each subtitle line. Likely causes: cache-key mismatch between parsed cue text and mpv `sub-text`, and priority-window selection skipping the currently active cue during mid-line starts/seeks.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Prefetched subtitle entries are reused when live subtitle text differs only by normalization details such as ASS `\N`, newline collapsing, or surrounding whitespace.
- [x] #2 Priority-window selection includes the currently active cue when playback starts or seeks into the middle of a cue.
- [x] #3 Regression tests cover the cache-hit normalization path and active-cue priority-window behavior.
- [x] #4 Verification covers the touched prefetch/controller lane.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add failing regression tests in `subtitle-processing-controller.test.ts` and `subtitle-prefetch.test.ts`.
2. Normalize cache keys in the subtitle processing controller so prefetch/live paths share keys.
3. Adjust prefetch priority-window selection to include the active cue.
4. Run targeted tests, then SubMiner verification lane for touched files.
<!-- SECTION:PLAN:END -->
## Outcome
<!-- SECTION:OUTCOME:BEGIN -->
Normalized subtitle cache keys inside the processing controller so prefetched ASS/VTT/live subtitle text variants reuse the same cache entry, and changed priority-window selection to include the currently active cue based on cue end time. Added regression coverage for both paths and verified the change with the `core` lane.
<!-- SECTION:OUTCOME:END -->

View File

@@ -1,49 +0,0 @@
---
id: TASK-197
title: Eliminate per-line plain subtitle flash on prefetch cache hit
status: Done
assignee: []
created_date: '2026-03-18 16:28'
updated_date: '2026-03-23 03:22'
labels: []
dependencies:
- TASK-196
references:
- >-
/home/sudacode/projects/japanese/SubMiner/src/core/services/subtitle-processing-controller.ts
- >-
/home/sudacode/projects/japanese/SubMiner/src/main/runtime/mpv-main-event-actions.ts
- >-
/home/sudacode/projects/japanese/SubMiner/src/main/runtime/mpv-main-event-main-deps.ts
priority: high
ordinal: 135500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Remove the remaining small per-line subtitle annotation delay after prefetch warmup by avoiding the unconditional plain-subtitle broadcast on mpv subtitle-change events when a cached annotated payload already exists.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 On a subtitle cache hit, the mpv subtitle-change path can emit annotated subtitle payload synchronously instead of first broadcasting `tokens: null`.
- [x] #2 Cache-miss behavior still preserves immediate plain-text subtitle display while async tokenization runs.
- [x] #3 Regression tests cover the controller cache-consume path and the mpv subtitle-change handler cache-hit branch.
- [x] #4 Verification covers the touched core/runtime lane.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add failing tests for controller cache consumption and mpv subtitle-change immediate annotated emission.
2. Add a controller method that consumes cached subtitle payload synchronously while updating internal latest/emitted state.
3. Wire the mpv subtitle-change handler to use the immediate cached payload when present, falling back to the existing plain-text path on misses.
4. Run focused tests and the cheapest sufficient verification lane.
<!-- SECTION:PLAN:END -->
## Outcome
<!-- SECTION:OUTCOME:BEGIN -->
Added `consumeCachedSubtitle` to the subtitle processing controller so cache hits can be claimed synchronously without reprocessing, then wired the mpv subtitle-change handler to emit cached annotated payloads immediately while preserving the existing plain-text fallback for misses. Verified with focused unit tests plus the `runtime-compat` lane.
<!-- SECTION:OUTCOME:END -->

View File

@@ -1,46 +0,0 @@
---
id: TASK-199
title: Forward launcher log level into mpv plugin script opts
status: Done
assignee: []
created_date: '2026-03-18 21:16'
updated_date: '2026-03-23 03:22'
labels: []
dependencies:
- TASK-198
references:
- /home/sudacode/projects/japanese/SubMiner/launcher/aniskip-metadata.ts
- /home/sudacode/projects/japanese/SubMiner/launcher/mpv.ts
- /home/sudacode/projects/japanese/SubMiner/launcher/main.test.ts
- /home/sudacode/projects/japanese/SubMiner/launcher/aniskip-metadata.test.ts
priority: medium
ordinal: 134500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Make `subminer --log-level=debug ...` reach the mpv plugin auto-start path by forwarding the launcher log level into `--script-opts`, so plugin-started overlay and texthooker subprocesses inherit debug logging.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Launcher mpv playback includes `subminer-log_level=<level>` in `--script-opts` when a non-info CLI log level is used.
- [x] #2 Detached idle mpv launch uses the same script-opt forwarding.
- [x] #3 Regression tests cover launcher script-opt forwarding.
<!-- AC:END -->
## Implementation Plan
<!-- SECTION:PLAN:BEGIN -->
1. Add a failing launcher regression test that captures mpv argv and expects `subminer-log_level=debug` inside `--script-opts`.
2. Extend the shared script-opt builder to accept launcher log level and emit `subminer-log_level` for non-info runs.
3. Reuse that builder in both normal mpv playback and detached idle mpv launch.
4. Run focused launcher tests and launcher-plugin verification.
<!-- SECTION:PLAN:END -->
## Outcome
<!-- SECTION:OUTCOME:BEGIN -->
Forwarded launcher log level into mpv plugin script opts via the shared builder and reused that builder for idle mpv launch. `subminer --log-level=debug ...` now gives the plugin `opts.log_level=debug`, so auto-started overlay and texthooker subprocesses include `--log-level debug` and the tokenizer timing logs can actually appear in the app log.
<!-- SECTION:OUTCOME: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,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-1
title: "Stats Dashboard"
---
## Description
Milestone: Stats Dashboard

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

@@ -1,19 +1,20 @@
---
id: TASK-100
title: Add configurable texthooker startup launch
title: 'Add configurable texthooker startup launch'
status: Done
assignee: []
created_date: '2026-03-06 23:30'
updated_date: '2026-03-16 05:13'
updated_date: '2026-03-07 01:59'
labels: []
dependencies: []
priority: medium
ordinal: 11010
ordinal: 10000
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add a config option under `texthooker` to launch the built-in texthooker server automatically when SubMiner starts.
Scope:
@@ -23,20 +24,26 @@ Scope:
- Start the existing texthooker server during normal app startup when enabled.
- Keep `texthooker.openBrowser` as separate behavior.
- Add regression coverage and update generated config docs/example.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Default config enables automatic texthooker startup.
- [x] #2 Config parser accepts valid boolean values and warns on invalid values.
- [x] #3 App-ready startup launches texthooker when enabled.
- [x] #4 Generated config template/example documents the new option.
<!-- AC:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Added `texthooker.launchAtStartup` with a default of `true`, wired it through config defaults/validation/template generation, and started the existing texthooker server during app-ready startup without coupling it to browser auto-open behavior.
Also added regression coverage for config parsing/template output and app-ready dependency wiring, then regenerated the checked-in config example artifacts.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -4,7 +4,7 @@ title: Index AniList character alternative names in the character dictionary
status: Done
assignee: []
created_date: '2026-03-07 00:00'
updated_date: '2026-03-16 05:13'
updated_date: '2026-03-08 00:11'
labels:
- dictionary
- anilist
@@ -13,17 +13,20 @@ references:
- src/main/character-dictionary-runtime.ts
- src/main/character-dictionary-runtime.test.ts
priority: high
ordinal: 71500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Index AniList character alternative names in generated character dictionaries so aliases like Shadow resolve during subtitle lookup instead of falling through to unrelated generic dictionary entries.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Character fetch reads AniList alternative character names needed for lookup coverage
- [x] #2 Generated term banks include alias-derived terms for subtitle lookups like シャドウ
- [x] #3 Regression coverage proves alternative-name indexing works end to end
@@ -32,9 +35,11 @@ Index AniList character alternative names in generated character dictionaries so
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Character dictionary generation now requests AniList `name.alternative`, indexes those aliases as term candidates, and expands mixed aliases like `Minoru Kagenou (影野ミノル)` into usable outer/inner variants. Also extended kana alias synthesis so the AniList alias `Shadow` emits `シャドウ`, which matches the subtitle token the user hit in The Eminence in Shadow.
Bumped the character-dictionary snapshot format to invalidate stale cached snapshots, and updated merged-dictionary rebuilds to refresh invalid snapshots before composing the ZIP so old cache files do not hard-fail the merge path.
Verified with `bun test src/main/character-dictionary-runtime.test.ts` and `bun run tsc --noEmit`.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -5,7 +5,7 @@ status: Done
assignee:
- codex
created_date: '2026-03-06 21:20'
updated_date: '2026-03-16 05:13'
updated_date: '2026-03-06 21:33'
labels: []
dependencies: []
references:
@@ -13,12 +13,12 @@ references:
- /home/sudacode/projects/japanese/SubMiner/src/core/services/cli-command.ts
- /home/sudacode/projects/japanese/SubMiner/src/main.ts
priority: medium
ordinal: 77500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Make the packaged Linux no-arg launch path behave like a quiet background start instead of surfacing startup-only noise.
Scope:
@@ -30,7 +30,9 @@ Scope:
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Initial background launch reaches the start path without logging `No running instance. Use --start to launch the app.`
- [x] #2 Default startup no longer emits the `Applied --password-store gnome-libsecret` line at normal log levels.
- [x] #3 Entry/background launch sanitization suppresses the observed `ExperimentalWarning: SQLite...` and `lsfg-vk ... unsupported configuration version` startup noise.
@@ -40,6 +42,7 @@ Scope:
## Implementation Notes
<!-- SECTION:NOTES:BEGIN -->
Normalized no-arg/password-store-only entry launches to append implicit `--start --background`, and upgraded `--background`-only entry launches to include `--start`.
Applied shared entry env sanitization before loading the main process so default startup strips the `lsfg-vk` Vulkan layer and sets `NODE_NO_WARNINGS=1`; background children keep the same sanitized env.
@@ -52,11 +55,13 @@ Verification:
- `bun run test:fast`
Note: the final `node --experimental-sqlite --test dist/main/runtime/registry.test.js` step in `bun run test:fast` still prints Node's own experimental SQLite warning because that test command explicitly enables the feature flag outside the app entrypoint.
<!-- SECTION:NOTES:END -->
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Default packaged startup is now quiet and behaves like an implicit `--start --background` launch.
- No-arg AppImage entry launches now append `--start --background`, and `--background`-only launches append the missing `--start`.

View File

@@ -5,24 +5,27 @@ status: Done
assignee:
- codex
created_date: '2026-03-07 02:20'
updated_date: '2026-03-16 05:13'
updated_date: '2026-03-07 02:20'
labels:
- texthooker
- websocket
- subtitle
dependencies: []
priority: medium
ordinal: 73500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Add a separate annotated subtitle websocket for bundled texthooker so token/JLPT/frequency markup is available on a stable dedicated port even when the regular websocket is in `auto` mode and skipped because `mpv_websocket` is installed.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Regular `websocket.enabled: "auto"` behavior remains unchanged and still skips the regular websocket when `mpv_websocket` is installed.
- [x] #2 A separate `annotationWebsocket` config controls an independent annotated websocket with default port `6678`.
- [x] #3 Bundled texthooker is pointed at the annotation websocket when it is enabled.
@@ -32,7 +35,9 @@ Add a separate annotated subtitle websocket for bundled texthooker so token/JLPT
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Added `annotationWebsocket.enabled`/`annotationWebsocket.port` with defaults of `true`/`6678`, started that websocket independently from the regular auto-managed websocket, and injected the bundled texthooker websocket URL so it connects to the annotation feed by default.
Also added focused regression coverage and regenerated the checked-in config examples.
<!-- SECTION:FINAL_SUMMARY:END -->

View File

@@ -5,7 +5,7 @@ status: Done
assignee:
- codex
created_date: '2026-03-06 21:45'
updated_date: '2026-03-16 05:13'
updated_date: '2026-03-06 21:45'
labels:
- texthooker
- subtitle
@@ -14,23 +14,24 @@ dependencies:
- TASK-103
references:
- /home/sudacode/projects/japanese/SubMiner/src/core/services/subtitle-ws.ts
- >-
/home/sudacode/projects/japanese/SubMiner/vendor/texthooker-ui/src/components/App.svelte
- >-
/home/sudacode/projects/japanese/SubMiner/vendor/texthooker-ui/src/line-markup.ts
- /home/sudacode/projects/japanese/SubMiner/vendor/texthooker-ui/src/components/App.svelte
- /home/sudacode/projects/japanese/SubMiner/vendor/texthooker-ui/src/line-markup.ts
- /home/sudacode/projects/japanese/SubMiner/vendor/texthooker-ui/src/app.css
priority: medium
ordinal: 76500
---
## Description
<!-- SECTION:DESCRIPTION:BEGIN -->
Bring bundled texthooker annotation rendering closer to the visible overlay. Keep the lightweight texthooker UX, but preserve token metadata for hover, match overlay color-precedence rules across known/N+1/name/frequency/JLPT, expose name-match highlighting as a toggle, and emit a structured annotation payload on the dedicated websocket so non-SubMiner clients can treat it as an API.
<!-- SECTION:DESCRIPTION:END -->
## Acceptance Criteria
<!-- AC:BEGIN -->
- [x] #1 Annotation websocket payload includes both rendered `sentence` HTML and structured token metadata for generic clients.
- [x] #2 Vendored texthooker preserves annotation metadata attrs needed for hover labels and uses overlay-matching color precedence rules.
- [x] #3 Vendored texthooker supports character-name highlighting with a user-facing toggle and standalone-web note.
@@ -41,5 +42,7 @@ Bring bundled texthooker annotation rendering closer to the visible overlay. Kee
## Final Summary
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
Extended the dedicated annotation websocket payload to ship `version`, plain `text`, rendered `sentence`, and structured `tokens` metadata while keeping backward-compatible `sentence` consumers working. Updated the vendored texthooker to preserve hover metadata attrs, follow overlay color precedence for known/N+1/name/frequency/JLPT annotations, add a character-name highlight toggle plus standalone-web dictionary note, and render lightweight hover labels for frequency/JLPT metadata. Added focused regression coverage and rebuilt both the vendored texthooker bundle and SubMiner.
<!-- SECTION:FINAL_SUMMARY:END -->

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