mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
Compare commits
57 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
7b5ab3294d
|
|||
|
2bbf38f987
|
|||
|
f09c91494d
|
|||
|
58ec9b76e0
|
|||
|
7a196f69d6
|
|||
| c799a8de3c | |||
|
34d2dce8dc
|
|||
|
3a22a97761
|
|||
|
962243e959
|
|||
|
021010a338
|
|||
|
4c0575afe0
|
|||
|
9e46176519
|
|||
|
f10e905dbd
|
|||
| e4aa8ff907 | |||
|
a6ece5388a
|
|||
|
6a44b54b51
|
|||
|
93cd688625
|
|||
|
8e319a417d
|
|||
|
38034db1e4
|
|||
|
f775f90360
|
|||
|
55dff6ced7
|
|||
|
d0c11d347b
|
|||
|
f0418c6e56
|
|||
| e18985fb14 | |||
|
2f07c3407a
|
|||
|
a5554ec530
|
|||
|
f9f2fe6e87
|
|||
|
8ca05859a9
|
|||
|
0cac446725
|
|||
|
23623ad1e1
|
|||
|
b623c5e160
|
|||
|
5436e0cd49
|
|||
|
beeeee5ebd
|
|||
|
fdbf769760
|
|||
|
0a36d1aa99
|
|||
|
69ab87c25f
|
|||
|
9a30419a23
|
|||
|
092c56f98f
|
|||
|
10ef535f9a
|
|||
|
6c80bd5843
|
|||
|
f0bd0ba355
|
|||
|
be4db24861
|
|||
|
83d21c4b6d
|
|||
|
e744fab067
|
|||
|
5167e3a494
|
|||
|
aff4e91bbb
|
|||
|
737101fe9e
|
|||
|
629fe97ef7
|
|||
|
fa97472bce
|
|||
|
83f13df627
|
|||
|
cde231b1ff
|
|||
|
7161fc3513
|
|||
|
9a91951656
|
|||
|
11e9c721c6
|
|||
|
3c66ea6b30
|
|||
|
79f37f3986
|
|||
|
f1b85b0751
|
3
.github/pull_request_template.md
vendored
Normal file
3
.github/pull_request_template.md
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
## Checklist
|
||||
|
||||
- [ ] Added a changelog fragment in `changes/`, or this PR is labeled `skip-changelog`
|
||||
19
.github/workflows/ci.yml
vendored
19
.github/workflows/ci.yml
vendored
@@ -13,6 +13,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- name: Setup Bun
|
||||
@@ -26,16 +27,24 @@ jobs:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
|
||||
vendor/subminer-yomitan/node_modules
|
||||
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
|
||||
|
||||
- name: Lint changelog fragments
|
||||
run: bun run changelog:lint
|
||||
|
||||
- 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, ',') }}"
|
||||
|
||||
- name: Build (TypeScript check)
|
||||
# Keep explicit typecheck for fast fail before full build/bundle.
|
||||
run: bun run tsc --noEmit
|
||||
run: bun run typecheck
|
||||
|
||||
- name: Test suite (source)
|
||||
run: bun run test:fast
|
||||
@@ -54,12 +63,12 @@ jobs:
|
||||
- name: Build (bundle)
|
||||
run: bun run build
|
||||
|
||||
- name: Immersion SQLite verification
|
||||
run: bun run test:immersion:sqlite:dist
|
||||
|
||||
- name: Dist smoke suite
|
||||
run: bun run test:smoke:dist
|
||||
|
||||
- name: Build docs
|
||||
run: bun run docs:build
|
||||
|
||||
- name: Security audit
|
||||
run: bun audit --audit-level high
|
||||
continue-on-error: true
|
||||
|
||||
266
.github/workflows/release.yml
vendored
266
.github/workflows/release.yml
vendored
@@ -10,6 +10,7 @@ concurrency:
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
@@ -26,22 +27,23 @@ jobs:
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
|
||||
vendor/subminer-yomitan/node_modules
|
||||
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
|
||||
|
||||
- name: Build (TypeScript check)
|
||||
run: bun run typecheck
|
||||
|
||||
- name: Test suite (source)
|
||||
run: bun run test:fast
|
||||
|
||||
@@ -59,6 +61,9 @@ jobs:
|
||||
- name: Build (bundle)
|
||||
run: bun run build
|
||||
|
||||
- name: Immersion SQLite verification
|
||||
run: bun run test:immersion:sqlite:dist
|
||||
|
||||
- name: Dist smoke suite
|
||||
run: bun run test:smoke:dist
|
||||
|
||||
@@ -83,7 +88,8 @@ jobs:
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
vendor/texthooker-ui/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'vendor/texthooker-ui/package.json') }}
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
@@ -98,8 +104,6 @@ jobs:
|
||||
|
||||
- name: Build AppImage
|
||||
run: bun run build:appimage
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build unversioned AppImage
|
||||
run: |
|
||||
@@ -139,7 +143,8 @@ jobs:
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
vendor/texthooker-ui/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'vendor/texthooker-ui/package.json') }}
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
@@ -175,7 +180,6 @@ jobs:
|
||||
- name: Build signed + notarized macOS artifacts
|
||||
run: bun run build:mac
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
@@ -190,8 +194,159 @@ jobs:
|
||||
release/*.dmg
|
||||
release/*.zip
|
||||
|
||||
build-windows:
|
||||
needs: [quality-gate]
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
vendor/texthooker-ui/node_modules
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Validate Windows signing secrets
|
||||
shell: bash
|
||||
run: |
|
||||
missing=0
|
||||
for name in SIGNPATH_API_TOKEN SIGNPATH_ORGANIZATION_ID SIGNPATH_PROJECT_SLUG SIGNPATH_SIGNING_POLICY_SLUG SIGNPATH_ARTIFACT_CONFIGURATION_SLUG; do
|
||||
if [ -z "${!name}" ]; then
|
||||
echo "Missing required secret: $name"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
if [ "$missing" -ne 0 ]; then
|
||||
echo "Set the SignPath Windows signing secrets and rerun."
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
SIGNPATH_API_TOKEN: ${{ secrets.SIGNPATH_API_TOKEN }}
|
||||
SIGNPATH_ORGANIZATION_ID: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
|
||||
SIGNPATH_PROJECT_SLUG: ${{ secrets.SIGNPATH_PROJECT_SLUG }}
|
||||
SIGNPATH_SIGNING_POLICY_SLUG: ${{ secrets.SIGNPATH_SIGNING_POLICY_SLUG }}
|
||||
SIGNPATH_ARTIFACT_CONFIGURATION_SLUG: ${{ secrets.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Build texthooker-ui
|
||||
shell: powershell
|
||||
run: |
|
||||
Set-Location vendor/texthooker-ui
|
||||
bun install
|
||||
bun run build
|
||||
|
||||
- name: Build unsigned Windows artifacts
|
||||
run: bun run build:win
|
||||
|
||||
- name: Upload unsigned Windows artifact for SignPath
|
||||
id: upload-unsigned-windows-artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unsigned-windows
|
||||
path: |
|
||||
release/*.exe
|
||||
release/*.zip
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Submit Windows signing request (attempt 1)
|
||||
id: signpath-sign-attempt-1
|
||||
continue-on-error: true
|
||||
uses: signpath/github-action-submit-signing-request@v2
|
||||
with:
|
||||
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
||||
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
|
||||
project-slug: ${{ secrets.SIGNPATH_PROJECT_SLUG }}
|
||||
signing-policy-slug: ${{ secrets.SIGNPATH_SIGNING_POLICY_SLUG }}
|
||||
artifact-configuration-slug: ${{ secrets.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }}
|
||||
github-artifact-id: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }}
|
||||
wait-for-completion: true
|
||||
output-artifact-directory: signed-windows-attempt-1
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Submit Windows signing request (attempt 2)
|
||||
id: signpath-sign-attempt-2
|
||||
if: steps.signpath-sign-attempt-1.outcome == 'failure'
|
||||
continue-on-error: true
|
||||
uses: signpath/github-action-submit-signing-request@v2
|
||||
with:
|
||||
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
||||
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
|
||||
project-slug: ${{ secrets.SIGNPATH_PROJECT_SLUG }}
|
||||
signing-policy-slug: ${{ secrets.SIGNPATH_SIGNING_POLICY_SLUG }}
|
||||
artifact-configuration-slug: ${{ secrets.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }}
|
||||
github-artifact-id: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }}
|
||||
wait-for-completion: true
|
||||
output-artifact-directory: signed-windows-attempt-2
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Submit Windows signing request (attempt 3)
|
||||
id: signpath-sign-attempt-3
|
||||
if: steps.signpath-sign-attempt-1.outcome == 'failure' && steps.signpath-sign-attempt-2.outcome == 'failure'
|
||||
continue-on-error: true
|
||||
uses: signpath/github-action-submit-signing-request@v2
|
||||
with:
|
||||
api-token: ${{ secrets.SIGNPATH_API_TOKEN }}
|
||||
organization-id: ${{ secrets.SIGNPATH_ORGANIZATION_ID }}
|
||||
project-slug: ${{ secrets.SIGNPATH_PROJECT_SLUG }}
|
||||
signing-policy-slug: ${{ secrets.SIGNPATH_SIGNING_POLICY_SLUG }}
|
||||
artifact-configuration-slug: ${{ secrets.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }}
|
||||
github-artifact-id: ${{ steps.upload-unsigned-windows-artifact.outputs.artifact-id }}
|
||||
wait-for-completion: true
|
||||
output-artifact-directory: signed-windows-attempt-3
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Fail when all SignPath signing attempts fail
|
||||
if: steps.signpath-sign-attempt-1.outcome == 'failure' && steps.signpath-sign-attempt-2.outcome == 'failure' && steps.signpath-sign-attempt-3.outcome == 'failure'
|
||||
shell: bash
|
||||
run: |
|
||||
echo "All SignPath signing attempts failed; rerun the workflow when SignPath is healthy."
|
||||
exit 1
|
||||
|
||||
- name: Upload signed Windows artifacts (attempt 1)
|
||||
if: steps.signpath-sign-attempt-1.outcome == 'success'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows
|
||||
path: |
|
||||
signed-windows-attempt-1/*.exe
|
||||
signed-windows-attempt-1/*.zip
|
||||
|
||||
- name: Upload signed Windows artifacts (attempt 2)
|
||||
if: steps.signpath-sign-attempt-2.outcome == 'success'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows
|
||||
path: |
|
||||
signed-windows-attempt-2/*.exe
|
||||
signed-windows-attempt-2/*.zip
|
||||
|
||||
- name: Upload signed Windows artifacts (attempt 3)
|
||||
if: steps.signpath-sign-attempt-3.outcome == 'success'
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows
|
||||
path: |
|
||||
signed-windows-attempt-3/*.exe
|
||||
signed-windows-attempt-3/*.zip
|
||||
|
||||
release:
|
||||
needs: [build-linux, build-macos]
|
||||
needs: [build-linux, build-macos, build-windows]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@@ -211,6 +366,12 @@ jobs:
|
||||
name: macos
|
||||
path: release
|
||||
|
||||
- name: Download Windows artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: windows
|
||||
path: release
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
@@ -249,7 +410,7 @@ jobs:
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
files=(release/*.AppImage release/*.dmg release/*.zip release/*.tar.gz dist/launcher/subminer)
|
||||
files=(release/*.AppImage release/*.dmg release/*.exe release/*.zip release/*.tar.gz dist/launcher/subminer)
|
||||
if [ "${#files[@]}" -eq 0 ]; then
|
||||
echo "No release artifacts found for checksum generation."
|
||||
exit 1
|
||||
@@ -260,63 +421,46 @@ jobs:
|
||||
id: version
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
- name: Verify changelog is ready for tagged release
|
||||
run: bun run changelog:check --version "${{ steps.version.outputs.VERSION }}"
|
||||
|
||||
- name: Generate release notes from changelog
|
||||
run: bun run changelog:release-notes --version "${{ steps.version.outputs.VERSION }}"
|
||||
|
||||
- name: Publish Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "")
|
||||
if [ -n "$PREV_TAG" ]; then
|
||||
CHANGES=$(git log --pretty=format:"- %s" ${PREV_TAG}..HEAD)
|
||||
set -euo pipefail
|
||||
|
||||
if gh release view "${{ steps.version.outputs.VERSION }}" >/dev/null 2>&1; then
|
||||
# Do not pass the prerelease flag here; gh defaults to a normal release.
|
||||
gh release edit "${{ steps.version.outputs.VERSION }}" \
|
||||
--draft=false \
|
||||
--title "${{ steps.version.outputs.VERSION }}" \
|
||||
--notes-file release/release-notes.md
|
||||
else
|
||||
COMMIT_COUNT=$(git rev-list --count HEAD)
|
||||
if [ "$COMMIT_COUNT" -gt 10 ]; then
|
||||
CHANGES=$(git log --pretty=format:"- %s" HEAD~10..HEAD)
|
||||
else
|
||||
CHANGES=$(git log --pretty=format:"- %s")
|
||||
fi
|
||||
gh release create "${{ steps.version.outputs.VERSION }}" \
|
||||
--title "${{ steps.version.outputs.VERSION }}" \
|
||||
--notes-file release/release-notes.md
|
||||
fi
|
||||
echo "CHANGES<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$CHANGES" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
name: ${{ steps.version.outputs.VERSION }}
|
||||
body: |
|
||||
## Changes
|
||||
${{ steps.changelog.outputs.CHANGES }}
|
||||
|
||||
## Installation
|
||||
|
||||
### AppImage (Recommended)
|
||||
1. Download the AppImage below
|
||||
2. Make it executable: `chmod +x SubMiner.AppImage`
|
||||
3. Run: `./SubMiner.AppImage`
|
||||
|
||||
### macOS
|
||||
1. Download `subminer-*.dmg`
|
||||
2. Open the DMG and drag `SubMiner.app` into `/Applications`
|
||||
3. If needed, use the ZIP artifact as an alternative
|
||||
|
||||
### Manual Installation
|
||||
See the [README](https://github.com/${{ github.repository }}#installation) for manual installation instructions.
|
||||
|
||||
### Optional Assets (config example + mpv plugin + rofi theme)
|
||||
1. Download `subminer-assets.tar.gz`
|
||||
2. Extract and copy `config.example.jsonc` to `~/.config/SubMiner/config.jsonc`
|
||||
3. Copy `plugin/subminer/` directory contents to `~/.config/mpv/scripts/`
|
||||
4. Copy `plugin/subminer.conf` to `~/.config/mpv/script-opts/`
|
||||
5. Copy `assets/themes/subminer.rasi` to:
|
||||
- Linux: `~/.local/share/SubMiner/themes/subminer.rasi`
|
||||
- macOS: `~/Library/Application Support/SubMiner/themes/subminer.rasi`
|
||||
|
||||
Note: the `subminer` wrapper script uses Bun (`#!/usr/bin/env bun`), so `bun` must be installed and on `PATH`.
|
||||
files: |
|
||||
shopt -s nullglob
|
||||
artifacts=(
|
||||
release/*.AppImage
|
||||
release/*.dmg
|
||||
release/*.exe
|
||||
release/*.zip
|
||||
release/*.tar.gz
|
||||
release/SHA256SUMS.txt
|
||||
dist/launcher/subminer
|
||||
draft: false
|
||||
prerelease: false
|
||||
)
|
||||
|
||||
if [ "${#artifacts[@]}" -eq 0 ]; then
|
||||
echo "No release artifacts found for upload."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
for asset in "${artifacts[@]}"; do
|
||||
gh release upload "${{ steps.version.outputs.VERSION }}" "$asset" --clobber
|
||||
done
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -5,6 +5,7 @@ node_modules/
|
||||
out/
|
||||
dist/
|
||||
release/
|
||||
build/yomitan/
|
||||
|
||||
# Launcher build artifact (produced by make build-launcher)
|
||||
/subminer
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -5,6 +5,6 @@
|
||||
[submodule "vendor/yomitan-jlpt-vocab"]
|
||||
path = vendor/yomitan-jlpt-vocab
|
||||
url = https://github.com/stephenmk/yomitan-jlpt-vocab
|
||||
[submodule "yomitan-jlpt-vocab"]
|
||||
path = vendor/yomitan-jlpt-vocab
|
||||
url = https://github.com/stephenmk/yomitan-jlpt-vocab
|
||||
[submodule "vendor/subminer-yomitan"]
|
||||
path = vendor/subminer-yomitan
|
||||
url = https://github.com/ksyasuda/subminer-yomitan
|
||||
|
||||
58
AGENTS.md
58
AGENTS.md
@@ -1,3 +1,61 @@
|
||||
# AGENTS.MD
|
||||
|
||||
## PR Feedback
|
||||
|
||||
- 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.
|
||||
- When merging a PR: thank the contributor in `CHANGELOG.md`.
|
||||
|
||||
## 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 repo’s package manager/runtime; no swaps w/o approval.
|
||||
- Use Codex background for long jobs; tmux only for interactive/persistent (debugger/server).
|
||||
|
||||
## Build / Test
|
||||
|
||||
- Before handoff: run full gate (lint/typecheck/tests/docs).
|
||||
- CI red: `gh run list/view`, rerun, fix, push, repeat til green.
|
||||
- Keep it observable (logs, panes, tails, MCP/browser tools).
|
||||
- Release: read `docs/RELEASING.md`
|
||||
|
||||
## Git
|
||||
|
||||
- Safe by default: `git status/diff/log`. Push only when user asks.
|
||||
- `git checkout` ok for PR review / explicit request.
|
||||
- Branch changes require user consent.
|
||||
- Destructive ops forbidden unless explicit (`reset --hard`, `clean`, `restore`, `rm`, …).
|
||||
- Don’t delete/rename unexpected stuff; stop + ask.
|
||||
- No repo-wide S/R scripts; keep edits small/reviewable.
|
||||
- Avoid manual `git stash`; if Git auto-stashes during pull/rebase, that’s fine (hint, not hard guardrail).
|
||||
- If user types a command (“pull and push”), that’s consent for that command.
|
||||
- No amend unless asked.
|
||||
- Big review: `git --no-pager diff --color=never`.
|
||||
- Multi-agent: check `git status/diff` before edits; ship small commits.
|
||||
|
||||
## 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.
|
||||
|
||||
## macOS Permissions / Signing (TCC)
|
||||
|
||||
- Never re-sign / ad-hoc sign / change bundle ID as “debug” without explicit ok (can mess TCC).
|
||||
|
||||
<!-- BACKLOG.MD MCP GUIDELINES START -->
|
||||
|
||||
<CRITICAL_INSTRUCTION>
|
||||
|
||||
66
CHANGELOG.md
Normal file
66
CHANGELOG.md
Normal file
@@ -0,0 +1,66 @@
|
||||
# Changelog
|
||||
|
||||
## v0.5.2 (2026-03-09)
|
||||
|
||||
### Internal
|
||||
- Release: Pinned the Windows SignPath submission workflow to an explicit artifact-configuration slug instead of relying on the SignPath project's default configuration.
|
||||
|
||||
## v0.5.1 (2026-03-09)
|
||||
|
||||
### Changed
|
||||
- Launcher: Removed the YouTube subtitle generation mode switch so YouTube playback always preloads subtitles before mpv starts.
|
||||
|
||||
### Fixed
|
||||
- Launcher: Hardened YouTube AI subtitle fixing so fenced SRT output and text-only one-cue-per-block responses can still be applied without losing original cue timing.
|
||||
- Launcher: Skipped AniSkip lookup during URL playback and YouTube subtitle-preload playback, limiting AniSkip to local file targets where it can actually resolve anime metadata.
|
||||
- Launcher: Keep the background SubMiner process running after a launcher-managed mpv session exits so the next mpv instance can reconnect without restarting the app.
|
||||
- Launcher: Reuse prior tokenization readiness after the background app is already warm so reopening a video does not pause again waiting for duplicate warmup completion.
|
||||
- Windows: Acquire the app single-instance lock earlier so Windows overlay/video launches reuse the running background SubMiner process instead of booting a second full app and repeating startup warmups.
|
||||
|
||||
## v0.3.0 (2026-03-05)
|
||||
- Added keyboard-driven Yomitan navigation and popup controls, including optional auto-pause.
|
||||
- Added subtitle/jump keyboard handling fixes for smoother subtitle playback control.
|
||||
- Improved Anki/Yomitan reliability with stronger Yomitan proxy syncing and safer extension refresh logic.
|
||||
- Added Subsync `replace` option and deterministic retime naming for subtitle workflows.
|
||||
- Moved aniskip resolution to launcher-script options for better control.
|
||||
- Tuned tokenizer frequency highlighting filters for improved term visibility.
|
||||
- Added release build quality-of-life for CLI publish (`gh`-based clobber upload).
|
||||
- Removed docs Plausible integration and cleaned associated tracker settings.
|
||||
|
||||
## v0.2.3 (2026-03-02)
|
||||
- Added performance and tokenization optimizations (faster warmup, persistent MeCab usage, reduced enrichment lookups).
|
||||
- Added subtitle controls for no-jump delay shifts.
|
||||
- Improved subtitle highlight logic with priority and reliability fixes.
|
||||
- Fixed plugin loading behavior to keep OSD visible during startup.
|
||||
- Fixed Jellyfin remote resume behavior and improved autoplay/tokenization interaction.
|
||||
- Updated startup flow to load dictionaries asynchronously and unblock first tokenization sooner.
|
||||
|
||||
## v0.2.2 (2026-03-01)
|
||||
- Improved subtitle highlighting reliability for frequency modes.
|
||||
- Fixed Jellyfin misc info formatting cleanup.
|
||||
- Version bump maintenance for 0.2.2.
|
||||
|
||||
## v0.2.1 (2026-03-01)
|
||||
- Delivered Jellyfin and Subsync fixes from release patch cycle.
|
||||
- Version bump maintenance for 0.2.1.
|
||||
|
||||
## v0.2.0 (2026-03-01)
|
||||
- Added task-related release work for the overlay 2.0 cycle.
|
||||
- Introduced Overlay 2.0.
|
||||
- Improved release automation reliability.
|
||||
|
||||
## v0.1.2 (2026-02-24)
|
||||
- Added encrypted AniList token handling and default GNOME keyring support.
|
||||
- Added launcher passthrough for password-store flows (Jellyfin path).
|
||||
- Updated docs for auth and integration behavior.
|
||||
- Version bump maintenance for 0.1.2.
|
||||
|
||||
## v0.1.1 (2026-02-23)
|
||||
- Fixed overlay modal focus handling (`grab input`) behavior.
|
||||
- Version bump maintenance for 0.1.1.
|
||||
|
||||
## v0.1.0 (2026-02-23)
|
||||
- Bootstrapped Electron runtime, services, and composition model.
|
||||
- Added runtime asset packaging and dependency vendoring.
|
||||
- Added project docs baseline, setup guides, architecture notes, and submodule/runtime assets.
|
||||
- Added CI release job dependency ordering fixes before launcher build.
|
||||
70
Makefile
70
Makefile
@@ -1,4 +1,4 @@
|
||||
.PHONY: help deps build build-launcher install build-linux build-macos build-macos-unsigned clean install-linux install-macos install-plugin uninstall uninstall-linux uninstall-macos print-dirs pretty ensure-bun generate-config generate-example-config docs-dev docs docs-preview docs-watch 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
|
||||
@@ -20,11 +20,6 @@ MACOS_DATA_DIR ?= $(HOME)/Library/Application Support/SubMiner
|
||||
MACOS_APP_DIR ?= $(HOME)/Applications
|
||||
MACOS_APP_DEST ?= $(MACOS_APP_DIR)/SubMiner.app
|
||||
|
||||
# mpv plugin install directories.
|
||||
MPV_CONFIG_DIR ?= $(HOME)/.config/mpv
|
||||
MPV_SCRIPTS_DIR ?= $(MPV_CONFIG_DIR)/scripts
|
||||
MPV_SCRIPT_OPTS_DIR ?= $(MPV_CONFIG_DIR)/script-opts
|
||||
|
||||
# If building from source, the AppImage will typically land in release/.
|
||||
APPIMAGE_SRC := $(firstword $(wildcard release/SubMiner-*.AppImage))
|
||||
MACOS_APP_SRC := $(firstword $(wildcard release/*.app release/*/*.app))
|
||||
@@ -41,6 +36,17 @@ else
|
||||
PLATFORM := unknown
|
||||
endif
|
||||
|
||||
WINDOWS_APPDATA ?= $(if $(APPDATA),$(subst \,/,$(APPDATA)),$(HOME)/AppData/Roaming)
|
||||
|
||||
# mpv plugin install directories.
|
||||
ifeq ($(PLATFORM),windows)
|
||||
MPV_CONFIG_DIR ?= $(WINDOWS_APPDATA)/mpv
|
||||
else
|
||||
MPV_CONFIG_DIR ?= $(HOME)/.config/mpv
|
||||
endif
|
||||
MPV_SCRIPTS_DIR ?= $(MPV_CONFIG_DIR)/scripts
|
||||
MPV_SCRIPT_OPTS_DIR ?= $(MPV_CONFIG_DIR)/script-opts
|
||||
|
||||
help:
|
||||
@printf '%s\n' \
|
||||
"Targets:" \
|
||||
@@ -56,12 +62,9 @@ help:
|
||||
" dev-watch-macos Start watch loop with forced macOS tracker backend" \
|
||||
" dev-toggle Toggle overlay in a running local Electron app" \
|
||||
" dev-stop Stop a running local Electron app" \
|
||||
" docs-dev Run VitePress docs dev server" \
|
||||
" docs-watch Run VitePress docs dev + Backlog browser together" \
|
||||
" docs Build VitePress static docs" \
|
||||
" docs-preview Preview built VitePress docs" \
|
||||
" install-linux Install Linux wrapper/theme/app artifacts" \
|
||||
" install-macos Install macOS wrapper/theme/app artifacts" \
|
||||
" install-windows Install Windows mpv plugin artifacts" \
|
||||
" install-plugin Install mpv Lua plugin and plugin config" \
|
||||
" generate-config Generate ~/.config/SubMiner/config.jsonc from centralized defaults" \
|
||||
"" \
|
||||
@@ -69,6 +72,7 @@ help:
|
||||
" 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" \
|
||||
"" \
|
||||
"Variables:" \
|
||||
@@ -78,7 +82,7 @@ help:
|
||||
" LINUX_DATA_DIR=... Override Linux app data dir" \
|
||||
" MACOS_DATA_DIR=... Override macOS app data dir" \
|
||||
" MACOS_APP_DIR=... Override macOS app install dir (default: $$HOME/Applications)" \
|
||||
" MPV_CONFIG_DIR=... Override mpv config dir (default: $$HOME/.config/mpv)"
|
||||
" MPV_CONFIG_DIR=... Override mpv config dir (default: $$HOME/.config/mpv or %APPDATA%/mpv on Windows)"
|
||||
|
||||
print-dirs:
|
||||
@printf '%s\n' \
|
||||
@@ -89,6 +93,10 @@ print-dirs:
|
||||
"MACOS_DATA_DIR=$(MACOS_DATA_DIR)" \
|
||||
"MACOS_APP_DIR=$(MACOS_APP_DIR)" \
|
||||
"MACOS_APP_DEST=$(MACOS_APP_DEST)" \
|
||||
"WINDOWS_APPDATA=$(WINDOWS_APPDATA)" \
|
||||
"MPV_CONFIG_DIR=$(MPV_CONFIG_DIR)" \
|
||||
"MPV_SCRIPTS_DIR=$(MPV_SCRIPTS_DIR)" \
|
||||
"MPV_SCRIPT_OPTS_DIR=$(MPV_SCRIPT_OPTS_DIR)" \
|
||||
"APPIMAGE_SRC=$(APPIMAGE_SRC)" \
|
||||
"MACOS_APP_SRC=$(MACOS_APP_SRC)" \
|
||||
"MACOS_ZIP_SRC=$(MACOS_ZIP_SRC)"
|
||||
@@ -102,13 +110,14 @@ ensure-bun:
|
||||
@command -v bun >/dev/null 2>&1 || { printf '%s\n' "[ERROR] bun not found"; exit 1; }
|
||||
|
||||
pretty: ensure-bun
|
||||
@bun run format
|
||||
@bun run format:src
|
||||
|
||||
build:
|
||||
@printf '%s\n' "[INFO] Detected platform: $(PLATFORM)"
|
||||
@case "$(PLATFORM)" in \
|
||||
linux) $(MAKE) --no-print-directory build-linux ;; \
|
||||
macos) $(MAKE) --no-print-directory build-macos ;; \
|
||||
windows) printf '%s\n' "[INFO] Windows builds run via: bun run build:win" ;; \
|
||||
*) printf '%s\n' "[ERROR] Unsupported OS for this Makefile target: $(PLATFORM)"; exit 1 ;; \
|
||||
esac
|
||||
|
||||
@@ -117,6 +126,7 @@ install:
|
||||
@case "$(PLATFORM)" in \
|
||||
linux) $(MAKE) --no-print-directory install-linux ;; \
|
||||
macos) $(MAKE) --no-print-directory install-macos ;; \
|
||||
windows) $(MAKE) --no-print-directory install-windows ;; \
|
||||
*) printf '%s\n' "[ERROR] Unsupported OS for this Makefile target: $(PLATFORM)"; exit 1 ;; \
|
||||
esac
|
||||
|
||||
@@ -158,18 +168,6 @@ generate-example-config: ensure-bun
|
||||
@bun run build
|
||||
@bun run generate:config-example
|
||||
|
||||
docs-dev: ensure-bun
|
||||
@bun run docs:dev
|
||||
|
||||
docs-watch: ensure-bun
|
||||
@bun run docs:watch
|
||||
|
||||
docs: ensure-bun
|
||||
@bun run docs:build
|
||||
|
||||
docs-preview: ensure-bun
|
||||
@bun run docs:preview
|
||||
|
||||
dev-start: ensure-bun
|
||||
@bun run build
|
||||
@bun run electron . --start
|
||||
@@ -226,18 +224,31 @@ install-macos: build-launcher
|
||||
fi
|
||||
@printf '%s\n' "Installed to:" " $(BINDIR)/subminer" " $(MACOS_DATA_DIR)/themes/$(THEME_FILE)" " $(MACOS_APP_DEST)"
|
||||
|
||||
install-windows:
|
||||
@printf '%s\n' "[INFO] Installing Windows mpv plugin artifacts"
|
||||
@$(MAKE) --no-print-directory install-plugin
|
||||
|
||||
install-plugin:
|
||||
@printf '%s\n' "[INFO] Installing mpv plugin artifacts"
|
||||
@install -d "$(MPV_SCRIPTS_DIR)"
|
||||
@rm -f "$(MPV_SCRIPTS_DIR)/subminer.lua"
|
||||
@rm -f "$(MPV_SCRIPTS_DIR)/subminer.lua" "$(MPV_SCRIPTS_DIR)/subminer-loader.lua"
|
||||
@install -d "$(MPV_SCRIPTS_DIR)/subminer"
|
||||
@install -d "$(MPV_SCRIPT_OPTS_DIR)"
|
||||
@cp -R ./plugin/subminer/. "$(MPV_SCRIPTS_DIR)/subminer/"
|
||||
@install -m 0644 "./$(PLUGIN_CONF)" "$(MPV_SCRIPT_OPTS_DIR)/subminer.conf"
|
||||
@if [ "$(PLATFORM)" = "windows" ]; then \
|
||||
bun ./scripts/configure-plugin-binary-path.mjs "$(MPV_SCRIPT_OPTS_DIR)/subminer.conf" "$(CURDIR)" win32; \
|
||||
fi
|
||||
@printf '%s\n' "Installed to:" " $(MPV_SCRIPTS_DIR)/subminer/main.lua" " $(MPV_SCRIPTS_DIR)/subminer/" " $(MPV_SCRIPT_OPTS_DIR)/subminer.conf"
|
||||
|
||||
# Uninstall behavior kept unchanged by default.
|
||||
uninstall: uninstall-linux
|
||||
uninstall:
|
||||
@printf '%s\n' "[INFO] Detected platform: $(PLATFORM)"
|
||||
@case "$(PLATFORM)" in \
|
||||
linux) $(MAKE) --no-print-directory uninstall-linux ;; \
|
||||
macos) $(MAKE) --no-print-directory uninstall-macos ;; \
|
||||
windows) $(MAKE) --no-print-directory uninstall-windows ;; \
|
||||
*) printf '%s\n' "[ERROR] Unsupported OS for this Makefile target: $(PLATFORM)"; exit 1 ;; \
|
||||
esac
|
||||
|
||||
uninstall-linux:
|
||||
@rm -f "$(BINDIR)/subminer" "$(BINDIR)/SubMiner.AppImage"
|
||||
@@ -249,3 +260,8 @@ uninstall-macos:
|
||||
@rm -f "$(MACOS_DATA_DIR)/themes/$(THEME_FILE)"
|
||||
@rm -rf "$(MACOS_APP_DEST)"
|
||||
@printf '%s\n' "Removed:" " $(BINDIR)/subminer" " $(MACOS_DATA_DIR)/themes/$(THEME_FILE)" " $(MACOS_APP_DEST)"
|
||||
|
||||
uninstall-windows:
|
||||
@rm -rf "$(MPV_SCRIPTS_DIR)/subminer"
|
||||
@rm -f "$(MPV_SCRIPTS_DIR)/subminer.lua" "$(MPV_SCRIPTS_DIR)/subminer-loader.lua" "$(MPV_SCRIPT_OPTS_DIR)/subminer.conf"
|
||||
@printf '%s\n' "Removed:" " $(MPV_SCRIPTS_DIR)/subminer" " $(MPV_SCRIPT_OPTS_DIR)/subminer.conf"
|
||||
|
||||
64
README.md
64
README.md
@@ -5,7 +5,7 @@
|
||||
<br /><br />
|
||||
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
[]()
|
||||
[]()
|
||||
[](https://docs.subminer.moe)
|
||||
|
||||
</div>
|
||||
@@ -20,11 +20,14 @@
|
||||
|
||||
<br />
|
||||
|
||||
Initial packaged Windows support is now available alongside the existing Linux and macOS builds.
|
||||
|
||||
## What it does
|
||||
|
||||
SubMiner is an Electron overlay that sits on top of mpv. It turns your video player into a full sentence-mining workstation:
|
||||
|
||||
- **Hover to look up** — Yomitan dictionary popups directly on subtitles
|
||||
- **Keyboard-driven lookup mode** — Navigate token-by-token, keep lookup open across tokens, and control popup scrolling/audio/mining without leaving the overlay
|
||||
- **One-key mining** — Creates Anki cards with sentence, audio, screenshot, and translation
|
||||
- **Instant auto-enrichment** — Optional local AnkiConnect proxy enriches new Yomitan cards immediately
|
||||
- **Reading annotations** — Combines N+1 targeting, frequency-dictionary highlighting, and JLPT underlining while you read
|
||||
@@ -32,6 +35,7 @@ SubMiner is an Electron overlay that sits on top of mpv. It turns your video pla
|
||||
- **Subtitle tools** — Download from Jimaku, sync with alass/ffsubsync
|
||||
- **Immersion tracking** — SQLite-powered stats on your watch time and mining activity
|
||||
- **Custom texthooker page** — Built-in custom texthooker page and websocket, no extra setup
|
||||
- **Annotated websocket API** — Dedicated annotation feed can serve bundled texthooker or external clients with rendered `sentence` HTML plus structured `tokens`
|
||||
- **Jellyfin integration** — Remote playback setup, cast device mode, and direct playback launch
|
||||
- **AniList progress** — Track episode completion and push watching progress automatically
|
||||
|
||||
@@ -52,30 +56,43 @@ chmod +x ~/.local/bin/subminer
|
||||
> [!NOTE]
|
||||
> The `subminer` wrapper uses a [Bun](https://bun.sh) shebang. Make sure `bun` is on your `PATH`.
|
||||
|
||||
**From source** or **macOS** — see the [installation guide](https://docs.subminer.moe/installation#from-source).
|
||||
**macOS (DMG/ZIP):** download the latest packaged build from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest) and drag `SubMiner.app` into `/Applications`.
|
||||
|
||||
### 2. Install the mpv plugin and configuration file
|
||||
**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`.
|
||||
|
||||
**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).
|
||||
|
||||
### 2. Launch the app once
|
||||
|
||||
```bash
|
||||
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer-assets.tar.gz -O /tmp/subminer-assets.tar.gz
|
||||
tar -xzf /tmp/subminer-assets.tar.gz -C /tmp
|
||||
mkdir -p ~/.config/mpv/scripts/subminer
|
||||
mkdir -p ~/.config/mpv/script-opts
|
||||
cp -R /tmp/plugin/subminer/. ~/.config/mpv/scripts/subminer/
|
||||
cp /tmp/plugin/subminer.conf ~/.config/mpv/script-opts/
|
||||
mkdir -p ~/.config/SubMiner && cp /tmp/config.example.jsonc ~/.config/SubMiner/config.jsonc
|
||||
# Linux
|
||||
SubMiner.AppImage
|
||||
```
|
||||
|
||||
### 3. Set up Yomitan Dictionaries
|
||||
On macOS, launch `SubMiner.app`. On Windows, launch `SubMiner.exe` from the Start menu or install directory.
|
||||
|
||||
```bash
|
||||
subminer app --yomitan
|
||||
```
|
||||
On first launch, SubMiner now:
|
||||
|
||||
- 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
|
||||
|
||||
Existing installs that already have a valid config plus at least one Yomitan dictionary are auto-detected as complete and will not be re-prompted.
|
||||
|
||||
### 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 app --start --background
|
||||
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
|
||||
```
|
||||
@@ -84,20 +101,33 @@ subminer --start video.mkv # optional explicit overlay start when plugin auto_st
|
||||
|
||||
| Required | Optional |
|
||||
| ------------------------------------------ | -------------------------------------------------- |
|
||||
| `bun` | |
|
||||
| `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
|
||||
|
||||
For full guides on configuration, Anki, Jellyfin, and more, see [docs.subminer.moe](https://docs.subminer.moe).
|
||||
|
||||
## Testing
|
||||
|
||||
- Run `bun run test` or `bun run test:fast` for the default fast lane: config/core coverage plus representative entry/runtime, Anki integration, and main runtime checks.
|
||||
- Run `bun run test:full` for the maintained test surface: Bun-compatible `src/**` coverage, Bun-compatible launcher unit coverage, and the maintained dist compatibility slice for `ipc`, `anki-jimaku-ipc`, `overlay-manager`, `config-validation`, `startup-config`, and runtime registry coverage.
|
||||
- Run `bun run test:node:compat` directly when you only need that dist compatibility slice. The command name is legacy; it now runs under Bun.
|
||||
- Run `bun run test:env` for environment-specific verification: launcher smoke/plugin checks plus the SQLite-backed immersion tracker lane.
|
||||
- Run `bun run test:immersion:sqlite` when you specifically need the dist SQLite persistence coverage.
|
||||
- Run `bun run test:subtitle` for the maintained `alass`/`ffsubsync` subtitle surface.
|
||||
|
||||
The Bun-managed discovery lanes intentionally exclude a small set of suites from the source-file discovery pass and keep them in the maintained dist compatibility slice instead: Electron-focused tests in `src/core/services/ipc.test.ts`, `src/core/services/anki-jimaku-ipc.test.ts`, and `src/core/services/overlay-manager.test.ts`, plus runtime/config tests in `src/main/config-validation.test.ts`, `src/main/runtime/startup-config.test.ts`, and `src/main/runtime/registry.test.ts`. `bun run test:node:compat` keeps those suites in the standard workflow instead of leaving them untracked.
|
||||
|
||||
## Acknowledgments
|
||||
|
||||
Built on the shoulders of [GameSentenceMiner](https://github.com/bpwhelan/GameSentenceMiner), [texthooker-ui](https://github.com/Renji-XD/texthooker-ui), [mpvacious](https://github.com/Ajatt-Tools/mpvacious), [Anacreon-Script](https://github.com/friedrich-de/Anacreon-Script), and [autosubsync-mpv](https://github.com/joaquintorres/autosubsync-mpv). Subtitles powered by [Jimaku.cc](https://jimaku.cc). Dictionary lookups via [Yomitan](https://github.com/yomidevs/yomitan).
|
||||
Built on the shoulders of [GameSentenceMiner](https://github.com/bpwhelan/GameSentenceMiner), [Renji's Texthooker Page](https://github.com/Renji-XD/texthooker-ui), [mpvacious](https://github.com/Ajatt-Tools/mpvacious), [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).
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 141 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,51 @@
|
||||
---
|
||||
id: TASK-87.7
|
||||
title: >-
|
||||
Developer workflow hygiene: make docs watch reproducible and remove stale
|
||||
small-surface drift
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:20'
|
||||
updated_date: '2026-03-06 03:21'
|
||||
labels:
|
||||
- tooling
|
||||
- tech-debt
|
||||
milestone: m-0
|
||||
dependencies: []
|
||||
references:
|
||||
- package.json
|
||||
- bun.lock
|
||||
- src/anki-integration/field-grouping-workflow.ts
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
parent_task_id: TASK-87
|
||||
priority: low
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
The review found a few low-risk but recurring hygiene issues: docs:watch depends on bunx concurrently even though concurrently is not declared in package metadata, and small stale API surface remains after recent refactors, such as unused parameters in field-grouping workflow code. This task should make the developer workflow reproducible and clean up low-risk stale symbols that do not warrant a dedicated architecture task.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [ ] #1 The docs:watch workflow runs through declared project tooling or is rewritten to avoid undeclared dependencies.
|
||||
- [ ] #2 Small stale symbols or parameters identified during the review outside the main composition-root cleanup are removed without behavior changes.
|
||||
- [ ] #3 Any contributor-facing command changes are reflected in repository documentation.
|
||||
- [ ] #4 The cleanup remains scoped to low-risk workflow and hygiene fixes rather than expanding into large architectural refactors.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
|
||||
1. Fix the docs:watch workflow so it relies on declared project tooling or an equivalent checked-in command path.
|
||||
2. Clean up low-risk stale symbols surfaced by the review outside the main.ts architecture task, such as unused parameters left behind by refactors.
|
||||
3. Keep the task scoped: avoid pulling in main composition-root cleanup or larger Anki/runtime refactors.
|
||||
4. Verify the affected developer commands still work and document any usage changes.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
@@ -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"
|
||||
|
||||
8
backlog/milestones/m-0 - codebase-health-remediation.md
Normal file
8
backlog/milestones/m-0 - codebase-health-remediation.md
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
id: m-0
|
||||
title: 'Codebase Health Remediation'
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
Follow-up work from the March 6, 2026 codebase review: strengthen the runnable test gate, remove confirmed dead architecture, and continue decomposition of oversized runtime entrypoints.
|
||||
@@ -0,0 +1,49 @@
|
||||
---
|
||||
id: TASK-100
|
||||
title: 'Add configurable texthooker startup launch'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-06 23:30'
|
||||
updated_date: '2026-03-07 01:59'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 10000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Add a config option under `texthooker` to launch the built-in texthooker server automatically when SubMiner starts.
|
||||
|
||||
Scope:
|
||||
|
||||
- Add `texthooker.launchAtStartup`.
|
||||
- Default to `true`.
|
||||
- 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 -->
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: TASK-101
|
||||
title: Index AniList character alternative names in the character dictionary
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-07 00:00'
|
||||
updated_date: '2026-03-08 00:11'
|
||||
labels:
|
||||
- dictionary
|
||||
- anilist
|
||||
dependencies: []
|
||||
references:
|
||||
- src/main/character-dictionary-runtime.ts
|
||||
- src/main/character-dictionary-runtime.test.ts
|
||||
priority: high
|
||||
---
|
||||
|
||||
## 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
|
||||
<!-- AC:END -->
|
||||
|
||||
## 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 -->
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
id: TASK-102
|
||||
title: Quiet default AppImage startup and implicit background launch
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-06 21:20'
|
||||
updated_date: '2026-03-06 21:33'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/main-entry-runtime.ts
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/core/services/cli-command.ts
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/main.ts
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## 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:
|
||||
|
||||
- Treat default background entry launches as implicit `--start --background`.
|
||||
- Keep the `--password-store` diagnostic out of normal startup output.
|
||||
- Suppress known startup-only `node:sqlite` and `lsfg-vk` warnings for the entry/background launch path.
|
||||
- Avoid noisy protocol-registration warnings during normal startup when registration is unsupported.
|
||||
<!-- 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.
|
||||
- [x] #4 Regression coverage documents the new startup behavior.
|
||||
<!-- AC:END -->
|
||||
|
||||
## 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.
|
||||
|
||||
Downgraded startup-only protocol-registration failure logging to debug, and routed the Linux password-store diagnostic through the scoped debug logger instead of raw console output.
|
||||
|
||||
Verification:
|
||||
|
||||
- `bun test src/main-entry-runtime.test.ts src/main/runtime/anilist-setup-protocol.test.ts src/main/runtime/anilist-setup-protocol-main-deps.test.ts`
|
||||
- `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`.
|
||||
- Entry/background startup sanitization now suppresses the observed `lsfg-vk` and `node:sqlite` warnings on the app launch path.
|
||||
- Linux password-store and unsupported protocol-registration diagnostics now stay at debug level instead of normal startup output.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: TASK-103
|
||||
title: Add dedicated annotation websocket for texthooker
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-07 02:20'
|
||||
updated_date: '2026-03-07 02:20'
|
||||
labels:
|
||||
- texthooker
|
||||
- websocket
|
||||
- subtitle
|
||||
dependencies: []
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## 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.
|
||||
- [x] #4 Focused regression tests cover config parsing, startup wiring, and texthooker bootstrap injection.
|
||||
<!-- AC:END -->
|
||||
|
||||
## 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 -->
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
id: TASK-104
|
||||
title: Mirror overlay annotation hover behavior in vendored texthooker
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-06 21:45'
|
||||
updated_date: '2026-03-06 21:45'
|
||||
labels:
|
||||
- texthooker
|
||||
- subtitle
|
||||
- websocket
|
||||
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/app.css
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## 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.
|
||||
- [x] #4 Hovering annotated texthooker tokens reveals JLPT/frequency metadata without adding the full overlay popup workflow.
|
||||
- [x] #5 Focused serializer, texthooker markup, socket parsing, CSS, and build verification pass.
|
||||
<!-- AC:END -->
|
||||
|
||||
## 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 -->
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
id: TASK-105
|
||||
title: Stop local docs artifact writes after docs repo split
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-07 00:00'
|
||||
updated_date: '2026-03-07 00:20'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 10500
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Now that user-facing docs live in `../subminer-docs`, first-party scripts in this repo should not keep writing generated artifacts into the local `docs/` tree.
|
||||
|
||||
Scope:
|
||||
|
||||
- Audit first-party scripts/automation for writes to `docs/`.
|
||||
- Keep repo-local outputs only where they are still intentionally owned by this repo.
|
||||
- Repoint generated docs artifacts to `../subminer-docs` when that is the maintained source of truth.
|
||||
- Add regression coverage for the config-example generation path contract.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 The config-example generator no longer writes to `docs/public/config.example.jsonc` inside this repo.
|
||||
- [x] #2 When `../subminer-docs` exists, the generator updates `../subminer-docs/public/config.example.jsonc`.
|
||||
- [x] #3 Automated coverage guards the output-path contract so local docs writes do not regress.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Removed the first-party local `docs/public` config-example write path from `src/generate-config-example.ts` and replaced it with sibling-docs-repo detection that targets `../subminer-docs/public/config.example.jsonc` only when that repo exists.
|
||||
|
||||
Added a project-local regression suite for output-path resolution and artifact writing, wired that suite into the maintained config test lane, and removed the stale generated `docs/public/config.example.jsonc` artifact from the working tree.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
id: TASK-106
|
||||
title: Add first-run setup gate and auto-install flow
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-07 06:10'
|
||||
updated_date: '2026-03-07 06:20'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/main.ts
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/shared/setup-state.ts
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/main/runtime/first-run-setup-service.ts
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/main/runtime/first-run-setup-window.ts
|
||||
- /home/sudacode/projects/japanese/SubMiner/launcher/commands/playback-command.ts
|
||||
priority: high
|
||||
ordinal: 10600
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Replace the current manual install flow with a first-run setup gate:
|
||||
|
||||
- bootstrap the default config dir/config file automatically
|
||||
- detect legacy installs and mark them complete when config + Yomitan dictionaries are already present
|
||||
- open a compact Catppuccin Macchiato setup popup for incomplete installs
|
||||
- optionally install the mpv plugin into the default mpv location
|
||||
- block launcher playback until setup completes, then resume the original playback flow
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 First app launch seeds the default config dir/config file without manual copy steps.
|
||||
- [x] #2 Existing installs with config plus at least one Yomitan dictionary are auto-detected as already complete.
|
||||
- [x] #3 Incomplete installs get a first-run setup popup with mpv plugin install, Yomitan settings, refresh, skip, and finish actions.
|
||||
- [x] #4 Launcher playback waits for setup completion and does not start mpv while setup is incomplete.
|
||||
- [x] #5 Plugin assets are packaged into the Electron bundle and regression tests cover the new flow.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Added shared setup-state/config/mpv path helpers so Electron and launcher read the same onboarding state file.
|
||||
|
||||
Introduced a first-run setup service plus compact BrowserWindow popup using Catppuccin Macchiato styling. The popup supports optional mpv plugin install, opening Yomitan settings, status refresh, skip-plugin, and gated finish once at least one Yomitan dictionary is installed.
|
||||
|
||||
Electron startup now bootstraps a default config file, auto-detects legacy-complete installs, adds `--setup` CLI support, exposes a tray `Complete Setup` action while incomplete, and avoids reopening setup once completion is recorded.
|
||||
|
||||
Launcher playback now checks the shared setup-state file before starting mpv. If setup is incomplete, it launches the app with `--background --setup`, waits for completion, and only then proceeds.
|
||||
|
||||
Verification:
|
||||
|
||||
- `bun run typecheck`
|
||||
- `bun run test:fast`
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
SubMiner now supports a download-and-launch install flow.
|
||||
|
||||
- First launch auto-creates config and opens setup only when needed.
|
||||
- Existing users with working installs are silently migrated to completed setup.
|
||||
- The setup popup handles optional mpv plugin install and Yomitan dictionary readiness.
|
||||
- Launcher playback is gated on setup completion and resumes automatically afterward.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,42 @@
|
||||
---
|
||||
id: TASK-107
|
||||
title: 'Fix Yomitan scan-token fallback fragmentation on exact-source misses'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-07 01:10'
|
||||
updated_date: '2026-03-07 01:12'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: high
|
||||
ordinal: 9007
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Left-to-right Yomitan scanning can emit bogus fallback tokens when `termsFind` returns entries but none of their headwords carries an exact primary source for the consumed substring. Repro: `だが それでも届かぬ高みがあった` currently yields trailing fragments like `があ` / `た`, which blocks the real `あった` token from receiving frequency highlighting.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Scanner skips `termsFind` fallback entries that are not backed by an exact primary source for the consumed substring.
|
||||
- [x] #2 Repro line no longer yields bogus trailing fragments such as `があ`.
|
||||
- [x] #3 Regression coverage added for the scan-token path.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Removed the scan-token helper fallback that previously emitted a token from the first returned headword even when Yomitan did not report an exact primary source for the consumed substring. Added a focused regression test covering `だが それでも届かぬ高みがあった`, ensuring bogus `があ` fragmentation is skipped so the later `あった` exact match can still be tokenized and highlighted.
|
||||
|
||||
Verification:
|
||||
|
||||
- `bun test src/core/services/tokenizer/yomitan-parser-runtime.test.ts src/core/services/tokenizer.test.ts --timeout 20000`
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
id: TASK-108
|
||||
title: 'Exclude single kana tokens from frequency highlighting'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-07 01:18'
|
||||
updated_date: '2026-03-07 01:22'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 9008
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Suppress frequency highlighting for single-character hiragana or katakana tokens. Scope is frequency-only: known/N+1/JLPT behavior stays unchanged.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Single-character hiragana tokens do not retain `frequencyRank`.
|
||||
- [x] #2 Single-character katakana tokens do not retain `frequencyRank`.
|
||||
- [x] #3 Regression coverage exists at annotation-stage and tokenizer levels.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Added a frequency-only suppression rule for single-character kana tokens based on token `surface`, so bogus merged fragments like `た` and standalone one-character kana no longer keep `frequencyRank`. Regression coverage now exists both in the annotation stage and in the tokenizer path, while multi-character tokens and N+1/JLPT behavior remain unchanged.
|
||||
|
||||
Verification:
|
||||
|
||||
- `bun test src/core/services/tokenizer/annotation-stage.test.ts --timeout 20000`
|
||||
- `bun test src/core/services/tokenizer.test.ts --timeout 20000`
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,51 @@
|
||||
---
|
||||
id: TASK-110
|
||||
title: Replace vendored Yomitan with submodule-built Chrome artifact workflow
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-07 11:05'
|
||||
updated_date: '2026-03-07 11:22'
|
||||
labels:
|
||||
- yomitan
|
||||
- build
|
||||
- release
|
||||
dependencies: []
|
||||
priority: high
|
||||
ordinal: 9010
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Replace the checked-in `vendor/yomitan` release tree with a `subminer-yomitan` git submodule. Build Yomitan from source, extract the Chromium zip artifact into a stable local build directory, and make SubMiner dev/runtime/tests/release packaging load that extracted extension instead of the source tree or vendored files.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Repo tracks Yomitan as a git submodule instead of committed extension files under `vendor/yomitan`.
|
||||
- [x] #2 SubMiner has a reproducible build/extract step that produces a local Chromium extension directory from `subminer-yomitan`.
|
||||
- [x] #3 Dev/runtime/tests resolve the extracted build output as the default Yomitan extension path.
|
||||
- [x] #4 Release packaging includes the extracted Chromium extension files instead of the old vendored tree.
|
||||
- [x] #5 Docs and verification commands reflect the new workflow.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Replaced the checked-in `vendor/yomitan` extension tree with a `vendor/subminer-yomitan` git submodule and added a reproducible `bun run build:yomitan` workflow that builds `yomitan-chrome.zip`, extracts it into `build/yomitan`, and reuses a source-state stamp to skip redundant rebuilds. Runtime path resolution, helper CLIs, Yomitan integration tests, packaging, CI cache keys, and README source-build notes now all target that generated artifact instead of the old vendored files.
|
||||
|
||||
Verification:
|
||||
|
||||
- `bun run build:yomitan`
|
||||
- `bun test src/core/services/yomitan-extension-paths.test.ts src/core/services/yomitan-structured-content-generator.test.ts src/yomitan-translator-sort.test.ts`
|
||||
- `bun run typecheck`
|
||||
- `bun run build`
|
||||
- `bun run test:core:src`
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
id: TASK-111
|
||||
title: Fix subtitle-cycle OSD labels for J keybindings
|
||||
status: Done
|
||||
assignee:
|
||||
- Codex
|
||||
created_date: '2026-03-07 23:45'
|
||||
updated_date: '2026-03-08 00:06'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- /Users/sudacode/projects/japanese/SubMiner/src/core/services/ipc-command.ts
|
||||
- /Users/sudacode/projects/japanese/SubMiner/src/core/services/mpv.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/core/services/ipc-command.test.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/core/services/mpv-control.test.ts
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
When cycling subtitle tracks with the default J/Shift+J keybindings, the mpv OSD currently shows raw template text like `${sid}` instead of a resolved subtitle label. Update the keybinding OSD behavior so users see the active subtitle selection clearly when cycling tracks, and ensure placeholder-based OSD messages sent through the mpv client API render correctly.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Pressing the primary subtitle cycle keybinding shows a resolved subtitle label on the OSD instead of a raw `${sid}` placeholder.
|
||||
- [x] #2 Pressing the secondary subtitle cycle keybinding shows a resolved subtitle label on the OSD instead of a raw `${secondary-sid}` placeholder.
|
||||
- [x] #3 Proxy OSD messages that rely on mpv property expansion render resolved values when sent through the mpv client API.
|
||||
- [x] #4 Regression tests cover the subtitle-cycle OSD behavior and the placeholder-expansion OSD path.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Add focused failing tests for subtitle-cycle OSD labels and mpv placeholder-expansion behavior.
|
||||
2. Update the IPC mpv command handler to resolve primary and secondary subtitle track labels from mpv `track-list` data after cycling subtitle tracks.
|
||||
3. Update the mpv OSD runtime path so placeholder-based `show-text` messages sent through the client API opt into property expansion.
|
||||
4. Run focused tests, then the relevant core test lane, and record results in the task notes.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Initial triage: `ipc-command.ts` emits raw `${sid}`/`${secondary-sid}` placeholder strings, and `showMpvOsdRuntime` sends `show-text` via mpv client API without enabling property expansion.
|
||||
|
||||
User approved implementation plan on 2026-03-07.
|
||||
|
||||
Implementation: proxy mpv command OSD now supports an async resolver so subtitle track cycling can show human-readable labels instead of raw `${sid}` placeholders.
|
||||
|
||||
Implementation: `showMpvOsdRuntime` now prefixes placeholder-based messages with mpv client-api `expand-properties`, which fixes raw `${...}` OSD output for subtitle delay/position messages.
|
||||
|
||||
Testing: `bun test src/core/services/ipc-command.test.ts src/core/services/mpv-control.test.ts src/main/runtime/mpv-proxy-osd.test.ts src/main/runtime/ipc-mpv-command-main-deps.test.ts src/main/runtime/ipc-bridge-actions.test.ts src/main/runtime/ipc-bridge-actions-main-deps.test.ts src/main/runtime/composers/ipc-runtime-composer.test.ts` passed.
|
||||
|
||||
Testing: `bun x tsc --noEmit` passed.
|
||||
|
||||
Testing: `bun run test:core:src` passed (423 pass, 6 skip, 0 fail).
|
||||
|
||||
Docs: no update required because no checked-in docs or help text describe the J/Shift+J OSD output behavior.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Fixed subtitle-cycle OSD handling for the default J/Shift+J keybindings. The IPC mpv command path now supports resolving proxy OSD text asynchronously, and the main-runtime resolver reads mpv `track-list` state so primary and secondary subtitle cycling show human-readable track labels instead of raw `${sid}` / `${secondary-sid}` placeholders.
|
||||
|
||||
Also fixed the lower-level mpv OSD transport so placeholder-based `show-text` messages sent through the client API opt into `expand-properties`. That preserves existing template-based OSD messages like subtitle delay and subtitle position without leaking the raw `${...}` syntax.
|
||||
|
||||
Added regression coverage for the async proxy OSD path, the placeholder-expansion `showMpvOsdRuntime` path, and the runtime subtitle-track label resolver. Verification run: `bun x tsc --noEmit`; focused mpv/IPC tests; and the maintained `bun run test:core:src` lane (423 pass, 6 skip, 0 fail).
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
id: TASK-112
|
||||
title: Address Claude review items on PR 15
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-08 00:11'
|
||||
updated_date: '2026-03-08 00:12'
|
||||
labels:
|
||||
- pr-review
|
||||
- ci
|
||||
dependencies: []
|
||||
references:
|
||||
- .github/workflows/release.yml
|
||||
- .github/workflows/ci.yml
|
||||
- .gitmodules
|
||||
- >-
|
||||
backlog/tasks/task-101 -
|
||||
Index-AniList-character-alternative-names-in-the-character-dictionary.md
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Review Claude's PR feedback on PR #15, implement only the technically valid fixes on the current branch, and document which comments are non-actionable or already acceptable.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Validated Claude's concrete PR review items against current branch state and repo conventions
|
||||
- [x] #2 Implemented the accepted fixes with regression coverage or verification where applicable
|
||||
- [x] #3 Documented which review items are non-blocking or intentionally left unchanged
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Validate each Claude review item against current branch files and repo workflow.
|
||||
2. Patch release quality-gate to match CI ordering and add explicit typecheck.
|
||||
3. Remove duplicate .gitmodules stanza and normalize the TASK-101 reference path through Backlog MCP.
|
||||
4. Run relevant verification for workflow/config metadata changes and record which review items remain non-actionable.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
User asked to address Claude PR comments on PR #15 and assess whether any action items remain. Treat review suggestions skeptically; only fix validated defects.
|
||||
|
||||
Validated Claude's five review items. Fixed release workflow ordering/typecheck, removed the duplicate .gitmodules entry, and normalized TASK-101 references to repo-relative paths via Backlog MCP.
|
||||
|
||||
Left the vendor/subminer-yomitan branch-pin suggestion unchanged. The committed submodule SHA already controls reproducibility; adding a branch would only affect update ergonomics and was not required to address a concrete defect.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Validated Claude's PR #15 review summary against the current branch and applied the actionable fixes. In `.github/workflows/release.yml`, the release `quality-gate` job now restores the dependency cache before installation, no longer installs twice, and runs `bun run typecheck` before the fast test suite to match CI expectations. In `.gitmodules`, removed the duplicate `vendor/yomitan-jlpt-vocab` stanza with the conflicting duplicate path. Through Backlog MCP, updated `TASK-101` references from an absolute local path to repo-relative paths so the task metadata is portable across contributors.
|
||||
|
||||
Verification: `git diff --check`, `git config -f .gitmodules --get-regexp '^submodule\..*\.path$'`, `bun run typecheck`, and `bun run test:fast` all passed. `bun run format:check` still fails on many pre-existing unrelated files already present on the branch, including multiple backlog task files and existing source/docs files; this review patch did not attempt a repo-wide formatting sweep.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
id: TASK-113
|
||||
title: Scope make pretty to maintained source files
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-08 00:20'
|
||||
updated_date: '2026-03-08 00:22'
|
||||
labels:
|
||||
- tooling
|
||||
- formatting
|
||||
dependencies: []
|
||||
references:
|
||||
- Makefile
|
||||
- package.json
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Change the `make pretty` workflow so it formats only the maintained source/config files we intentionally keep under Prettier, instead of sweeping backlog/docs/generated content across the whole repository.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 `make pretty` formats only the approved maintained source/config paths
|
||||
- [x] #2 The allowlist is reusable for check/write flows instead of duplicating path logic
|
||||
- [x] #3 Verification shows the scoped formatting command targets the intended files without touching backlog or vendored content
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Inspect current Prettier config/ignore behavior and keep the broad repo-wide format command unchanged.
|
||||
2. Add a reusable scoped Prettier script that targets maintained source/config paths only.
|
||||
3. Update `make pretty` to call the scoped script.
|
||||
4. Verify the scoped command resolves only intended files and does not traverse backlog or vendor paths.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
User approved the allowlist approach: keep repo-wide `format` intact, make `make pretty` use a maintained-path formatter scope.
|
||||
|
||||
Added `scripts/prettier-scope.sh` as the single allowlist for scoped Prettier paths and wired `format:src` / `format:check:src` to it.
|
||||
|
||||
Updated `make pretty` to call `bun run format:src`. Verified with `make -n pretty` and shell tracing that the helper only targets the maintained allowlist and does not traverse `backlog/` or `vendor/`.
|
||||
|
||||
Excluded `Makefile` and `.prettierignore` from the allowlist after verification showed Prettier cannot infer parsers for them.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Scoped the repo's day-to-day formatting entrypoint without changing the existing broad repo-wide Prettier scripts. Added `scripts/prettier-scope.sh` as the shared allowlist for maintained source/config paths (`.github`, `build`, `launcher`, `scripts`, `src`, plus selected root JSON config files), added `format:src` and `format:check:src` in `package.json`, and updated `make pretty` to run the scoped formatter.
|
||||
|
||||
Verification: `make -n pretty` now resolves to `bun run format:src`. `bash -n scripts/prettier-scope.sh` passed, and shell-traced `bash -x scripts/prettier-scope.sh --check` confirmed the exact allowlist passed to Prettier. `bun run format:check:src` fails only because existing files inside the allowed source scope are not currently formatted; it no longer touches `backlog/` or `vendor/`.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
62
backlog/tasks/task-114 - Fix-failing-CI-checks-on-PR-15.md
Normal file
62
backlog/tasks/task-114 - Fix-failing-CI-checks-on-PR-15.md
Normal file
@@ -0,0 +1,62 @@
|
||||
---
|
||||
id: TASK-114
|
||||
title: Fix failing CI checks on PR 15
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-08 00:34'
|
||||
updated_date: '2026-03-08 00:37'
|
||||
labels:
|
||||
- ci
|
||||
- test
|
||||
dependencies: []
|
||||
references:
|
||||
- src/renderer/subtitle-render.test.ts
|
||||
- src/renderer/style.css
|
||||
- .github/workflows/ci.yml
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Investigate the failing GitHub Actions CI run for PR #15 on branch `yomitan-fork`, fix the underlying test or code regression, and verify the affected local test/CI lane passes.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Identified the concrete failing CI job and captured the relevant failure context
|
||||
- [x] #2 Implemented the minimal code or test change needed to resolve the CI failure
|
||||
- [x] #3 Verified the affected local test target and the broader fast CI test lane pass
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Inspect the failing GitHub Actions run and confirm the exact failing test/assertion.
|
||||
2. Reproduce the failing renderer stylesheet test locally and compare the assertion against current CSS.
|
||||
3. Apply the minimal test or stylesheet fix needed to restore the intended hover/selection behavior.
|
||||
4. Re-run the targeted renderer test, then re-run `bun run test` to verify the fast CI lane is green.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
GitHub Actions run 22810400921 failed in job build-test-audit, step `Test suite (source)`, with a single failing test: `JLPT CSS rules use underline-only styling in renderer stylesheet` in src/renderer/subtitle-render.test.ts.
|
||||
|
||||
Reproduced the failing test locally with `bun test src/renderer/subtitle-render.test.ts`. The failure was a brittle stylesheet assertion, not a renderer behavior regression.
|
||||
|
||||
Updated the renderer stylesheet test helper to split selectors safely across `:is(...)` commas and normalize multiline selector whitespace, then switched the failing hover/JLPT assertions to inspect extracted rule blocks instead of matching the entire CSS file text.
|
||||
|
||||
Verification passed with `bun test src/renderer/subtitle-render.test.ts` and `bun run test`.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Investigated GitHub Actions CI run `22810400921` for PR #15 and confirmed the only failing job was `build-test-audit`, step `Test suite (source)`, with a single failure in `src/renderer/subtitle-render.test.ts` (`JLPT CSS rules use underline-only styling in renderer stylesheet`).
|
||||
|
||||
The renderer CSS itself was still correct; the regression was in the test helper. `extractClassBlock` was splitting selector lists on every comma, which breaks selectors containing `:is(...)`, and the affected assertions fell back to brittle whole-file regex matching against a multiline selector. Fixed the test by teaching the helper to split selectors only at top-level commas, normalizing selector whitespace around multiline `:not(...)` / `:is(...)` clauses, and asserting on extracted rule blocks for the plain-word hover and JLPT-only hover/selection rules.
|
||||
|
||||
Verification: `bun test src/renderer/subtitle-render.test.ts` passed, and `bun run test` passed end to end (the same fast lane that failed in CI).
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
id: TASK-115
|
||||
title: Refresh subminer-docs contributor docs for current repo workflow
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-08 00:40'
|
||||
updated_date: '2026-03-08 00:42'
|
||||
labels:
|
||||
- docs
|
||||
dependencies: []
|
||||
references:
|
||||
- ../subminer-docs/development.md
|
||||
- ../subminer-docs/README.md
|
||||
- Makefile
|
||||
- package.json
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Update the sibling `subminer-docs` repo so contributor/development docs match the current SubMiner repo workflow after the docs split and recent tooling changes, including removing stale in-repo docs build steps and documenting the scoped formatting command.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Contributor docs in `subminer-docs` no longer reference stale in-repo docs build commands for the app repo
|
||||
- [x] #2 Contributor docs mention the current scoped formatting workflow (`make pretty` / `format:src`) where relevant
|
||||
- [x] #3 Removed stale or no-longer-needed instructions that no longer match the current repo layout
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Inspect `subminer-docs` for contributor/development instructions that drifted after the docs repo split and recent tooling changes.
|
||||
2. Update contributor docs to remove stale app-repo docs commands and document the current scoped formatting workflow.
|
||||
3. Verify the modified docs page and build the docs site from the sibling docs repo when local dependencies are available.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Detected concrete doc drift in `subminer-docs/development.md`: stale in-repo docs build commands and no mention of the scoped `make pretty` formatter.
|
||||
|
||||
Updated `../subminer-docs/development.md` to remove stale app-repo docs build steps from the local gate, document `make pretty` / `format:check:src`, and point docs-site work to the sibling docs repo explicitly.
|
||||
|
||||
Installed docs repo dependencies locally with `bun install` and verified the docs site with `bun run docs:build` in `../subminer-docs`.
|
||||
|
||||
Did not change `../subminer-docs/README.md`; it was already accurate for the docs repo itself.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Refreshed the contributor/development docs in the sibling `subminer-docs` repo to match the current SubMiner workflow. In `development.md`, removed the stale app-repo `bun run docs:build` step from the local CI-equivalent gate, added an explicit note to run docs builds from `../subminer-docs` when docs change, documented the scoped formatting workflow (`make pretty` and `bun run format:check:src`), and replaced the old in-repo `make docs*` instructions with the correct sibling-repo `bun run docs:*` commands. Also updated the Makefile reference to include `make pretty` and removed the obsolete `make docs-dev` entry.
|
||||
|
||||
Verification: installed docs repo dependencies with `bun install` in `../subminer-docs` and ran `bun run docs:build` successfully. Left `README.md` unchanged because it was already accurate for the standalone docs repo.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
id: TASK-116
|
||||
title: Audit branch commits for remaining subminer-docs updates
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-08 00:46'
|
||||
updated_date: '2026-03-08 00:48'
|
||||
labels:
|
||||
- docs
|
||||
dependencies: []
|
||||
references:
|
||||
- ../subminer-docs/installation.md
|
||||
- ../subminer-docs/troubleshooting.md
|
||||
- src/core/services/yomitan-extension-paths.ts
|
||||
- scripts/build-yomitan.mjs
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Review recent `yomitan-fork` commits against the sibling `subminer-docs` repo, identify any concrete documentation drift that remains after the earlier contributor-doc updates, and patch the docs for behavior/tooling changes that are now outdated or misleading.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Reviewed recent branch commits for user-facing or contributor-facing changes that may require docs updates
|
||||
- [x] #2 Updated `subminer-docs` pages where branch changes introduced concrete doc drift
|
||||
- [x] #3 Verified the docs site still builds after the updates
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Review branch commit themes against `subminer-docs` and identify only concrete drift introduced by recent workflow/runtime changes.
|
||||
2. Patch docs for the Yomitan submodule build workflow, updated source-build prerequisites, and current runtime Yomitan search paths/manual fallback path.
|
||||
3. Rebuild the docs site to verify the updated pages render cleanly.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Concrete remaining drift after commit audit: installation/development docs still understate the Node/npm + submodule requirements for the Yomitan build flow, and troubleshooting still points at obsolete `vendor/yomitan` / `extensions/yomitan` paths.
|
||||
|
||||
Audited branch commits against subminer-docs coverage. Existing docs already cover first-run setup, texthooker startup/annotated websocket config, AniList merged character dictionaries, configurable collapsible sections, and subtitle name highlighting. Patched remaining drift around source-build prerequisites and Yomitan build/install paths in installation.md, development.md, and troubleshooting.md. Verified with `bun run docs:build` in ../subminer-docs.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Audited branch commits for missing documentation updates in ../subminer-docs. Updated installation, development, and troubleshooting docs to match the current Yomitan submodule build flow, source-build prerequisites, and runtime extension search/manual fallback paths. Confirmed other recent branch features were already documented and rebuilt the docs site successfully.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,59 @@
|
||||
---
|
||||
id: TASK-117
|
||||
title: Prepare initial Windows release docs and version bump
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-08 15:17'
|
||||
updated_date: '2026-03-08 15:17'
|
||||
labels:
|
||||
- release
|
||||
- docs
|
||||
- windows
|
||||
dependencies: []
|
||||
references:
|
||||
- package.json
|
||||
- README.md
|
||||
- ../subminer-docs/installation.md
|
||||
- ../subminer-docs/usage.md
|
||||
- ../subminer-docs/changelog.md
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Prepare the initial packaged Windows release by bumping the app version and refreshing the release-facing README/backlog/docs surfaces so install and direct-command guidance no longer reads Linux-only.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 App version is bumped for the Windows release cut
|
||||
- [x] #2 README and sibling docs describe Windows packaged installation alongside Linux/macOS guidance
|
||||
- [x] #3 Backlog records the release-doc/version update with the modified references
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Bump the package version for the release cut.
|
||||
2. Update the root README install/start guidance to mention Windows packaged builds.
|
||||
3. Patch the sibling docs repo installation, usage, and changelog pages for the Windows release.
|
||||
4. Record the work in Backlog and run targeted verification on the touched surfaces.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
The public README still advertised Linux/macOS only, while the sibling docs had Windows-specific runtime notes but no actual Windows install section and several direct-command examples still assumed `SubMiner.AppImage`.
|
||||
|
||||
Bumped `package.json` to `0.5.0`, expanded the README platform/install copy to include Windows, added a Windows install section to `../subminer-docs/installation.md`, clarified in `../subminer-docs/usage.md` that direct packaged-app examples use `SubMiner.exe` on Windows, and added a `v0.5.0` changelog entry covering the initial Windows release plus the latest overlay behavior polish.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Prepared the initial Windows release documentation pass and version bump. `package.json` now reports `0.5.0`. The root `README.md` now advertises Linux, macOS, and Windows support, includes Windows packaged-install guidance, and clarifies first-launch behavior across platforms. In the sibling docs repo, `installation.md` now includes a dedicated Windows install section, `usage.md` explains that direct packaged-app examples use `SubMiner.exe` on Windows, and `changelog.md` now includes the `v0.5.0` release notes for the initial Windows build and recent overlay behavior changes.
|
||||
|
||||
Verification: targeted `bun run tsc --noEmit -p tsconfig.typecheck.json` in the app repo and `bun run docs:build` in `../subminer-docs`.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
id: TASK-117
|
||||
title: >-
|
||||
Replace YouTube subtitle generation with pure TypeScript pipeline and shared
|
||||
AI config
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-08 03:16'
|
||||
updated_date: '2026-03-08 03:35'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- /Users/sudacode/projects/japanese/SubMiner/launcher/youtube.ts
|
||||
- /Users/sudacode/projects/japanese/SubMiner/src/anki-integration/ai.ts
|
||||
- /Users/sudacode/projects/japanese/SubMiner/src/types.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/config/definitions/defaults-integrations.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/config/resolve/subtitle-domains.ts
|
||||
- /Users/sudacode/projects/japanese/SubMiner/config.example.jsonc
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Replace the launcher YouTube subtitle generation flow with a pure TypeScript pipeline that prefers real downloadable YouTube subtitles, never uses YouTube auto-generated subtitles, locally generates missing tracks with whisper.cpp, and can optionally fix generated subtitles via a shared OpenAI-compatible AI provider config. This feature also introduces a breaking config cleanup: move provider settings to a new top-level ai section and reduce ankiConnect.ai to a boolean feature toggle.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Launcher YouTube subtitle generation prefers downloadable manual YouTube subtitles, never uses YouTube auto-generated subtitles, and locally generates only missing tracks with whisper.cpp.
|
||||
- [x] #2 Generated whisper subtitle tracks can optionally be post-processed with an OpenAI-compatible AI provider using shared top-level ai config, with validation and fallback to raw whisper output on failure.
|
||||
- [x] #3 Configuration is updated so top-level ai is canonical shared provider config, ankiConnect.ai is boolean-only, and youtubeSubgen includes whisperVadModel, whisperThreads, and fixWithAi.
|
||||
- [x] #4 Launcher CLI/config parsing, config example, and docs reflect the new breaking config shape with no migration layer.
|
||||
- [x] #5 Automated tests cover the new YouTube generation behavior, AI-fix fallback/validation behavior, shared AI config usage, and breaking config validation.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Introduce canonical top-level ai config plus youtubeSubgen runtime knobs (whisperVadModel, whisperThreads, fixWithAi) and convert ankiConnect.ai to a boolean-only toggle across types, defaults, validation, option registries, launcher config parsing, and config example/docs.
|
||||
2. Extract shared OpenAI-compatible AI client helpers from the current Anki translation code, including base URL normalization, API key / apiKeyCommand resolution, timeout handling, and response text extraction.
|
||||
3. Update Anki translation flow and hot-reload/runtime plumbing to consume global ai config while treating ankiConnect.ai as a feature gate only.
|
||||
4. Replace launcher/youtube.ts with a modular launcher/youtube pipeline that fetches only manual YouTube subtitles, generates missing tracks locally with ffmpeg + whisper.cpp + optional VAD/thread controls, and preserves preprocess/automatic playback behavior.
|
||||
5. Add optional AI subtitle-fix processing for whisper-generated tracks using the shared ai client, with strict SRT batching/validation and fallback to raw whisper output on provider or format failure.
|
||||
6. Expand automated coverage for config validation, shared AI usage, launcher config parsing, and YouTube subtitle generation behavior including removal of yt-dlp auto-subs and AI-fix fallback rules.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Implemented pure TypeScript launcher/youtube pipeline modules for manual subtitle fetch, audio extraction, whisper runs, SRT utilities, and optional AI subtitle fixing. Removed yt-dlp auto-subtitle usage from the generation path.
|
||||
|
||||
Added shared top-level ai config plus shared AI client helpers; converted ankiConnect.ai to a boolean feature gate and updated Anki runtime wiring to consume global ai config.
|
||||
|
||||
Updated launcher config parsing, config template sections, and config.example.jsonc for the breaking config shape including youtubeSubgen.whisperVadModel, youtubeSubgen.whisperThreads, and youtubeSubgen.fixWithAi.
|
||||
|
||||
Verification: bun run test:config:src passed; targeted AI/Anki/runtime tests passed; bun run typecheck passed. bun run test:launcher:unit:src reported one unrelated existing failure in launcher/aniskip-metadata.test.ts (resolveAniSkipMetadataForFile resolves MAL id and intro payload).
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Replaced the launcher YouTube subtitle flow with a modular TypeScript pipeline that prefers manual YouTube subtitles, transcribes only missing tracks with whisper.cpp, and can optionally post-fix whisper output through a shared OpenAI-compatible AI client with strict SRT validation/fallback. Introduced canonical top-level ai config, reduced ankiConnect.ai to a boolean feature gate, updated launcher/config parsing and checked-in config artifacts, and added coverage for YouTube orchestration, whisper args, SRT validation, AI fix behavior, and breaking config validation.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
id: TASK-117.1
|
||||
title: Harden AI subtitle fix against non-SRT model responses
|
||||
status: Done
|
||||
assignee:
|
||||
- '@codex'
|
||||
created_date: '2026-03-08 08:22'
|
||||
updated_date: '2026-03-08 08:25'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/launcher/youtube/subtitle-fix-ai.ts
|
||||
- /Users/sudacode/projects/japanese/SubMiner/launcher/youtube/srt.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/launcher/youtube/subtitle-fix-ai.test.ts
|
||||
parent_task_id: TASK-117
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Prevent optional YouTube AI subtitle post-processing from bailing out whenever the model returns usable cue text in a non-SRT wrapper or text-only format. The launcher should recover safe cases, preserve original timing, and fall back cleanly when the response cannot be mapped back to the source cues.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 AI subtitle fixing accepts safe AI responses that omit SRT framing but still provide one corrected text payload per original cue while preserving original cue timing.
|
||||
- [x] #2 AI subtitle fixing still rejects responses that cannot be mapped back to the original cue batch without guessing and falls back to the raw subtitle file with a warning.
|
||||
- [x] #3 Automated tests cover wrapped-SRT and text-only AI responses plus an unrecoverable invalid response case.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Add failing tests in launcher/youtube/subtitle-fix-ai.test.ts for three cases: wrapped valid SRT, text-only one-block-per-cue output, and unrecoverable invalid output.
|
||||
2. Extend launcher/youtube/subtitle-fix-ai.ts with a small response-normalization path that first strips markdown/code-fence wrappers, then accepts deterministic text-only cue batches only when they map 1:1 to the original cues without changing timestamps.
|
||||
3. Keep existing safety rules: preserve cue count and timing, log a warning, and fall back to the raw subtitle file when normalization cannot recover a trustworthy batch.
|
||||
4. Run focused launcher unit tests for subtitle-fix-ai and SRT parsing; expand only if the change affects adjacent behavior.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Implemented deterministic AI subtitle-response recovery for fenced SRT, embedded SRT payloads, and text-only 1:1 cue batches while preserving original timing and existing fallback behavior.
|
||||
|
||||
Verification: bun test launcher/youtube/*.test.ts passed; bun run typecheck passed; repo-wide format check still reports unrelated pre-existing warnings in launcher/youtube/orchestrator.ts and scripts/build-changelog*.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Hardened the launcher AI subtitle-fix path so it can recover deterministic non-SRT model responses instead of immediately falling back. Added `parseAiSubtitleFixResponse` in `launcher/youtube/subtitle-fix-ai.ts` to normalize markdown-fenced or embedded SRT payloads first, then accept text-only responses only when they map 1:1 onto the original cue batch and preserve source timings. Added regression coverage in `launcher/youtube/subtitle-fix-ai.test.ts` for fenced SRT, text-only cue batches, and unrecoverable invalid output, plus a changelog fragment in `changes/task-117.1.md`.
|
||||
|
||||
Verification: `bun test launcher/youtube/*.test.ts`, `bun run typecheck`, `bunx prettier --check launcher/youtube/subtitle-fix-ai.ts launcher/youtube/subtitle-fix-ai.test.ts`, and `bun run changelog:lint` passed. Repo-wide `bun run format:check:src` still reports unrelated pre-existing warnings in `launcher/youtube/orchestrator.ts` and `scripts/build-changelog*`.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
id: TASK-118
|
||||
title: Add Windows release build and SignPath signing
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-08 15:17'
|
||||
updated_date: '2026-03-08 15:17'
|
||||
labels:
|
||||
- release
|
||||
- windows
|
||||
- signing
|
||||
dependencies: []
|
||||
references:
|
||||
- .github/workflows/release.yml
|
||||
- build/installer.nsh
|
||||
- build/signpath-windows-artifact-config.xml
|
||||
- package.json
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Extend the tag-driven release workflow so Windows artifacts are built on GitHub-hosted runners and submitted to SignPath for free open-source Authenticode signing, while preserving the existing macOS notarization path.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Release workflow builds Windows installer and ZIP artifacts on `windows-latest`
|
||||
- [x] #2 Workflow submits unsigned Windows artifacts to SignPath and uploads the signed outputs for release publication
|
||||
- [x] #3 Repository includes a checked-in SignPath artifact-configuration source of truth for the Windows release files
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Inspect the existing release workflow and current Windows packaging configuration.
|
||||
2. Add a Windows release job that builds unsigned artifacts, uploads them as a workflow artifact, and submits them to SignPath.
|
||||
3. Update the release aggregation job to publish signed Windows assets and mention Windows install steps in the generated release notes.
|
||||
4. Check in the Windows SignPath artifact configuration XML used to define what gets signed.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
The repository already had Windows packaging configuration (`build:win`, NSIS include script, Windows helper asset packaging), but the release workflow still built Linux and macOS only.
|
||||
|
||||
Added a `build-windows` job to `.github/workflows/release.yml` that runs on `windows-latest`, validates required SignPath secrets, builds unsigned Windows artifacts, uploads them with `actions/upload-artifact@v4`, and then calls the official `signpath/github-action-submit-signing-request@v2` action to retrieve signed outputs.
|
||||
|
||||
Checked in `build/signpath-windows-artifact-config.xml` as the source-of-truth artifact configuration for SignPath. It signs the top-level NSIS installer EXE and deep-signs `.exe` and `.dll` files inside the portable ZIP artifact.
|
||||
|
||||
Updated the release aggregation job to download the signed Windows artifacts and added a Windows install section to the generated GitHub release body.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Windows release publishing is now wired into the tag-driven workflow. `.github/workflows/release.yml` builds Windows artifacts on `windows-latest`, submits them to SignPath using the official GitHub action, and publishes the signed `.exe` and `.zip` outputs alongside the Linux and macOS artifacts. The workflow now requests the additional `actions: read` permission required by the SignPath GitHub integration, and the generated release notes now include Windows installation steps.
|
||||
|
||||
The checked-in `build/signpath-windows-artifact-config.xml` file defines the SignPath artifact structure expected by the workflow artifact ZIP: sign the top-level `SubMiner-*.exe` installer and deep-sign `.exe` and `.dll` files inside `SubMiner-*.zip`.
|
||||
|
||||
Verification: workflow/static changes were checked with `git diff --check` on the touched files. Actual signing requires configured SignPath secrets and a matching artifact configuration in your SignPath project.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
id: TASK-118
|
||||
title: Fix GitHub release workflow publish step failure
|
||||
status: Done
|
||||
assignee:
|
||||
- Codex
|
||||
created_date: '2026-03-08 03:34'
|
||||
updated_date: '2026-03-08 03:38'
|
||||
labels:
|
||||
- ci
|
||||
- release
|
||||
- github-actions
|
||||
dependencies: []
|
||||
references:
|
||||
- /Users/sudacode/projects/japanese/SubMiner/.github/workflows/release.yml
|
||||
- 'https://github.com/ksyasuda/SubMiner/actions/runs/22812335927'
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
The GitHub Actions Release workflow fails during the Publish Release step for tag releases because the gh CLI invocation passes invalid arguments when creating or editing the GitHub release. Restore successful release publication for tagged builds without changing unrelated release packaging behavior.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Tagged Release workflow completes the Publish Release step without gh CLI argument errors.
|
||||
- [x] #2 Release workflow still creates or updates the GitHub release as a non-prerelease for normal version tags.
|
||||
- [x] #3 A regression check covers the publish command shape or workflow behavior that caused this failure.
|
||||
- [x] #4 Any release workflow behavior change is documented in repository docs or workflow comments if needed.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Add a targeted regression test for .github/workflows/release.yml that fails if the publish step passes an argument to the gh --prerelease boolean flag or otherwise omits explicit non-prerelease behavior.
|
||||
2. Run the targeted test to confirm the current workflow fails for the expected reason.
|
||||
3. Patch the Publish Release step in .github/workflows/release.yml to remove the invalid gh CLI usage while preserving non-prerelease release creation/update behavior.
|
||||
4. Re-run the targeted regression test and any relevant lightweight verification, then record results in task notes.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Identified root cause from GitHub Actions run 22812335927: Publish Release failed with `accepts 1 arg(s), received 2` because the workflow passed a value to gh's boolean prerelease flag.
|
||||
|
||||
Added a workflow comment clarifying that omitting the prerelease flag keeps normal releases as non-prerelease releases.
|
||||
|
||||
Added src/release-workflow.test.ts and wired it into `bun run test:fast` so CI catches the invalid workflow shape before the next tag.
|
||||
|
||||
Verification: `bun test src/release-workflow.test.ts`, `bun run typecheck`, and `bun run test:fast` all passed locally.
|
||||
|
||||
Code-review pass found no issues; remaining caveat is that prerelease tag semantics are still not modeled for tags like `v1.0.0-beta.1`, which is outside this fix scope.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Fixed the GitHub Actions release publish step so tagged releases no longer fail on invalid gh CLI usage. The workflow now omits the prerelease flag when creating or editing normal releases, which preserves existing non-prerelease behavior and avoids the `accepts 1 arg(s), received 2` failure seen in run 22812335927.
|
||||
|
||||
Added a small regression test that reads `.github/workflows/release.yml` and asserts the publish step does not set the prerelease flag, then included that test in `bun run test:fast` so the main verification lane catches this class of workflow regression before the next release.
|
||||
|
||||
Validation run locally: `bun test src/release-workflow.test.ts`, `bun run typecheck`, and `bun run test:fast`. Residual risk: prerelease-tag semantics remain unchanged for tags such as `v1.0.0-beta.1`; this fix is intentionally scoped to restoring normal tagged release publication.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
id: TASK-119
|
||||
title: Add Jellyfin remote-session subtitle streaming to texthooker
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-08 03:46'
|
||||
labels:
|
||||
- jellyfin
|
||||
- texthooker
|
||||
- subtitle
|
||||
dependencies: []
|
||||
references:
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/main/runtime/jellyfin-remote-commands.ts
|
||||
- /Users/sudacode/projects/japanese/SubMiner/src/core/services/jellyfin.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/core/services/subtitle-processing-controller.ts
|
||||
- 'https://api.jellyfin.org/'
|
||||
documentation:
|
||||
- 'https://api.jellyfin.org/'
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Allow SubMiner to follow subtitles from a separate Jellyfin client session, such as a TV app, without requiring local mpv playback. The feature should fetch the active subtitle stream from Jellyfin, map the remote playback position to subtitle cues, and feed the existing subtitle tokenization plus annotated texthooker websocket pipeline so texthooker-only mode can be used while watching on another device.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 User can target a remote Jellyfin session and stream its current subtitle cue into SubMiner's existing subtitle-processing pipeline without launching local Jellyfin playback in mpv.
|
||||
- [ ] #2 Texthooker-only mode can display subtitle updates from the tracked remote Jellyfin session through the existing annotation websocket feed.
|
||||
- [ ] #3 Remote session changes are handled safely: item changes, subtitle-track changes, pause/seek/stop, and session disconnects clear or refresh subtitle state without crashing.
|
||||
- [ ] #4 The feature degrades clearly when the remote session has no usable text subtitle stream or uses an unsupported subtitle format.
|
||||
- [ ] #5 Automated tests cover session tracking, subtitle cue selection, and feed integration; user-facing docs/config docs are updated.
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: TASK-120
|
||||
title: 'Replace node:sqlite with libsql and remove Yomitan Node wrapper'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-08 04:14'
|
||||
updated_date: '2026-03-08 04:39'
|
||||
labels:
|
||||
- runtime
|
||||
- bun
|
||||
- sqlite
|
||||
- tech-debt
|
||||
dependencies: []
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Remove the remaining root Node requirement caused by immersion tracking SQLite usage and the old Yomitan build wrapper by migrating the local SQLite layer off node:sqlite, running the SQLite-backed verification lanes under Bun, and switching the vendored Yomitan build flow to Bun-native scripts.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Immersion tracker runtime no longer imports or requires node:sqlite
|
||||
- [x] #2 SQLite-backed immersion tracker tests run under Bun without Node --experimental-sqlite
|
||||
- [x] #3 Root build/test scripts no longer require the Yomitan Node wrapper or Node-based SQLite verification lanes
|
||||
- [x] #4 README requirements/testing docs reflect the Bun-native workflow
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Replaced the immersion tracker SQLite dependency with a local libsql-backed wrapper, updated Bun/runtime compatibility tests to avoid process.exitCode side effects, switched Yomitan builds to run directly inside the vendored Bun-native project, deleted scripts/build-yomitan.mjs, and verified typecheck plus Bun build/test lanes (`build:yomitan`, `test:immersion:sqlite`, `test:runtime:compat`, `test:fast`).
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
id: TASK-121
|
||||
title: >-
|
||||
Fix YouTube manual subtitle selection regression when downloadable tracks
|
||||
exist
|
||||
status: Done
|
||||
assignee:
|
||||
- '@codex'
|
||||
created_date: '2026-03-08 05:37'
|
||||
updated_date: '2026-03-08 05:42'
|
||||
labels:
|
||||
- bug
|
||||
- youtube
|
||||
- subtitles
|
||||
dependencies: []
|
||||
references:
|
||||
- /Users/sudacode/projects/japanese/SubMiner/launcher/youtube/manual-subs.ts
|
||||
- /Users/sudacode/projects/japanese/SubMiner/launcher/youtube/orchestrator.ts
|
||||
- 'https://www.youtube.com/watch?v=MXzQRLmN9hE'
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Ensure launcher YouTube subtitle generation reuses downloadable manual subtitle tracks when the video already has requested languages available, instead of falling back to whisper generation. Reproduce against videos like MXzQRLmN9hE that expose manual en/ja subtitles via yt-dlp.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 When requested primary/secondary manual YouTube subtitle tracks exist, planning selects them and schedules no whisper generation for those tracks.
|
||||
- [x] #2 Filename normalization handles manual subtitle outputs produced by yt-dlp for language-tagged downloads.
|
||||
- [x] #3 Automated tests cover the reproduced manual en/ja selection case.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Reproduced against https://www.youtube.com/watch?v=MXzQRLmN9hE with yt-dlp --list-subs: manual zh/en/ja/ko subtitle tracks are available from YouTube.
|
||||
|
||||
Adjusted launcher YouTube orchestration so detected manual subtitle tracks suppress whisper generation but are no longer materialized as external subtitle files. SubMiner now relies on the native YouTube/mpv subtitle tracks for those languages.
|
||||
|
||||
Added orchestration tests covering the manual-track reuse plan and ran a direct runtime probe against MXzQRLmN9hE. Probe result: primary/secondary native tracks detected, no external subtitle aliases emitted, output directory remained empty.
|
||||
|
||||
Verification: bun test launcher/youtube/orchestrator.test.ts launcher/config-domain-parsers.test.ts launcher/mpv.test.ts passed; bun run typecheck passed.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Fixed the YouTube subtitle regression where videos with real downloadable subtitle tracks still ended up with duplicate external subtitle files. Manual subtitle availability now suppresses whisper generation and external subtitle publication, so videos like MXzQRLmN9hE use the native YouTube/mpv subtitle tracks directly. Launcher preprocess logging was also updated to report native subtitle availability instead of misleading missing statuses.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,71 @@
|
||||
---
|
||||
id: TASK-122
|
||||
title: Harden changelog workflow and CI enforcement
|
||||
status: Done
|
||||
assignee:
|
||||
- Codex
|
||||
created_date: '2026-03-08 06:13'
|
||||
updated_date: '2026-03-08 06:28'
|
||||
labels:
|
||||
- release
|
||||
- changelog
|
||||
- ci
|
||||
dependencies: []
|
||||
references:
|
||||
- /Users/sudacode/projects/japanese/SubMiner/scripts/build-changelog.ts
|
||||
- /Users/sudacode/projects/japanese/SubMiner/scripts/build-changelog.test.ts
|
||||
- /Users/sudacode/projects/japanese/SubMiner/.github/workflows/ci.yml
|
||||
- /Users/sudacode/projects/japanese/SubMiner/.github/workflows/release.yml
|
||||
- /Users/sudacode/projects/japanese/SubMiner/docs/RELEASING.md
|
||||
- /Users/sudacode/projects/japanese/SubMiner/changes/README.md
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Improve the release changelog workflow so changelog fragments are reliable, release output is more readable, and pull requests get early feedback when changelog metadata is missing or malformed.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 `scripts/build-changelog.ts` ignores non-fragment files in `changes/` and validates fragment structure before generating changelog output.
|
||||
- [x] #2 Generated `CHANGELOG.md` and `release/release-notes.md` group public changes into readable sections instead of a flat bullet list.
|
||||
- [x] #3 CI enforces changelog validation on pull requests and provides an explicit opt-out path for changes that should not produce release notes.
|
||||
- [x] #4 Contributor docs explain the fragment format and the PR/release workflow for changelog generation.
|
||||
- [x] #5 Automated tests cover fragment parsing/building behavior and workflow enforcement expectations.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Add failing tests for changelog fragment discovery, structured fragment parsing/rendering, release-note output, and CI workflow expectations.
|
||||
2. Update scripts/build-changelog.ts to ignore non-fragment files, parse fragment metadata, group generated output by change type, add lint/PR-check commands, and simplify output paths to repo-local artifacts.
|
||||
3. Update CI and PR workflow files to run changelog validation on pull requests with an explicit skip path, and keep release workflow using committed changelog output.
|
||||
4. Refresh changes/README.md, docs/RELEASING.md, and any PR template text so contributors know how to write fragments and when opt-out is allowed.
|
||||
5. Run targeted tests and changelog commands, then record results and finalize the task.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Implemented structured changelog fragments with required `type` and `area` metadata; `changes/README.md` is now ignored by the generator and verified by regression tests.
|
||||
|
||||
Added `changelog:lint` and `changelog:pr-check`, plus PR CI enforcement with `skip-changelog` opt-out. PR check now reads git name-status output so deleted fragment files do not satisfy the requirement.
|
||||
|
||||
Changed generated changelog/release notes output to grouped sections (`Added`, `Changed`, `Fixed`, etc.) and simplified release notes to highlights + install/assets pointers.
|
||||
|
||||
Kept changelog output repo-local. This aligns with existing repo direction where docs updates happen in the sibling docs repo explicitly rather than implicit local writes from app-repo generators.
|
||||
|
||||
Verification: `bun test scripts/build-changelog.test.ts src/ci-workflow.test.ts src/release-workflow.test.ts` passed; `bun run typecheck` passed; `bun run changelog:lint` passed. `bun run test:fast` still fails in unrelated existing `src/core/services/subsync.test.ts` cases (`runSubsyncManual keeps internal alass source file alive until sync finishes`, `runSubsyncManual resolves string sid values from mpv stream properties`).
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Hardened the changelog workflow end-to-end. `scripts/build-changelog.ts` now ignores helper files like `changes/README.md`, requires structured fragment metadata (`type` + `area`), groups generated release sections by change type, and emits shorter release notes focused on highlights plus install/assets pointers. Added explicit `changelog:lint` and `changelog:pr-check` commands, with PR validation based on git name-status so deleted fragment files do not satisfy the fragment requirement.
|
||||
|
||||
Updated contributor-facing workflow docs in `changes/README.md`, `docs/RELEASING.md`, and a new PR template so authors know to add a fragment or apply the `skip-changelog` label. CI now runs fragment linting on every run and enforces fragment presence on pull requests. Added regression coverage in `scripts/build-changelog.test.ts` and a new `src/ci-workflow.test.ts` to lock the workflow contract.
|
||||
|
||||
Verification completed: `bun test scripts/build-changelog.test.ts src/ci-workflow.test.ts src/release-workflow.test.ts`, `bun run typecheck`, and `bun run changelog:lint` all passed. A broader `bun run test:fast` run still fails in unrelated existing `src/core/services/subsync.test.ts` cases outside the changelog/workflow scope.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
id: TASK-123
|
||||
title: Add progress logging for YouTube subtitle generation phases
|
||||
status: Done
|
||||
assignee:
|
||||
- '@codex'
|
||||
created_date: '2026-03-08 07:07'
|
||||
updated_date: '2026-03-08 07:15'
|
||||
labels:
|
||||
- ux
|
||||
- logging
|
||||
- youtube
|
||||
- subtitles
|
||||
dependencies: []
|
||||
references:
|
||||
- /Users/sudacode/projects/japanese/SubMiner/launcher/youtube/orchestrator.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/launcher/youtube/audio-extraction.ts
|
||||
- /Users/sudacode/projects/japanese/SubMiner/launcher/youtube/whisper.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/launcher/youtube/subtitle-fix-ai.ts
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Improve launcher YouTube subtitle generation observability so users can tell that work is happening and roughly how long each phase is taking. Cover manual subtitle probe, audio extraction, ffmpeg prep, whisper generation, and optional AI subtitle fix phases without flooding normal logs.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Users see clear info-level phase logs for YouTube subtitle generation work including subtitle probe, fallback audio extraction, whisper, and optional AI fix phases.
|
||||
- [x] #2 Long-running phases surface elapsed-time progress or explicit start/finish timing so it is obvious the process is still active.
|
||||
- [x] #3 Automated tests cover the new logging/progress helper behavior where practical.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Implemented a shared timed YouTube phase logger in launcher/youtube/progress.ts with info-level start/finish messages and warn-level failure messages that include elapsed time.
|
||||
|
||||
Wired phase logging into YouTube metadata probe, manual subtitle probe, fallback audio extraction, ffmpeg whisper prep, whisper primary/secondary generation, and optional AI subtitle fix phases.
|
||||
|
||||
Verification: bun test launcher/youtube/progress.test.ts launcher/youtube/orchestrator.test.ts passed; bun run typecheck passed.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Added clear phase-level observability for YouTube subtitle generation without noisy tool output. Users now see start/finish logs with elapsed time for subtitle probe, fallback audio extraction, ffmpeg prep, whisper generation, and optional AI subtitle-fix phases, making it obvious when generation is active and roughly how long each step took.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
id: TASK-124
|
||||
title: >-
|
||||
Remove YouTube subtitle generation modes and make YouTube playback always
|
||||
generate/load subtitles
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-08 07:18'
|
||||
updated_date: '2026-03-08 07:28'
|
||||
labels:
|
||||
- launcher
|
||||
- youtube
|
||||
- subtitles
|
||||
dependencies: []
|
||||
references:
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/launcher/commands/playback-command.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/launcher/config/args-normalizer.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/launcher/config/youtube-subgen-config.ts
|
||||
- /Users/sudacode/projects/japanese/SubMiner/launcher/types.ts
|
||||
- /Users/sudacode/projects/japanese/SubMiner/config.example.jsonc
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/config/definitions/options-integrations.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/config/resolve/subtitle-domains.ts
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Simplify launcher YouTube playback by removing the configurable subtitle generation mode. For YouTube targets, the launcher should treat subtitle generation/loading as the canonical behavior instead of supporting off/preprocess/automatic branches. This change should remove the unreliable automatic/background path and the mode concept from config/CLI/env/docs, while preserving the core YouTube subtitle generation pipeline and mpv loading flow.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Launcher playback no longer supports or branches on a YouTube subtitle generation mode; YouTube URLs follow a single generation-and-load flow.
|
||||
- [x] #2 Configuration, CLI parsing, and environment handling no longer expose a YouTube subtitle generation mode option, and stale automatic/preprocess/off values are not part of the supported interface.
|
||||
- [x] #3 Tests cover the new single-flow behavior and the removal of mode parsing/branching.
|
||||
- [x] #4 User-facing config/docs/examples are updated to reflect the removed mode concept.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Remove the YouTube subtitle generation mode concept from launcher/shared types, config parsing, CLI options, and environment normalization so no supported interface accepts automatic/preprocess/off.
|
||||
2. Update playback orchestration so YouTube targets always run subtitle generation/loading before mpv startup and delete the background automatic path.
|
||||
3. Adjust mpv YouTube URL argument construction to no longer branch on mode while preserving subtitle/audio language behavior and preloaded subtitle file injection.
|
||||
4. Add/modify tests first to cover removed mode parsing and the single YouTube preload flow, then update config/docs/examples to match the simplified interface.
|
||||
5. Run focused launcher/config tests plus typecheck, then summarize any remaining gaps.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Removed launcher/shared youtubeSubgen.mode handling and collapsed YouTube playback onto a single preload-before-mpv subtitle generation flow.
|
||||
|
||||
Added launcher integration coverage proving YouTube subtitle generation runs before mpv startup and that the removed --mode flag now errors.
|
||||
|
||||
Verification: bun test launcher/config-domain-parsers.test.ts launcher/parse-args.test.ts launcher/mpv.test.ts launcher/main.test.ts src/config/config.test.ts; bun run test:config:src; bun run typecheck.
|
||||
|
||||
Broader repo checks still show pre-existing issues outside this change: bun run test:launcher:unit:src fails in launcher/aniskip-metadata.test.ts (MAL id assertion), and format scope check reports unrelated existing files launcher/youtube/orchestrator.ts, scripts/build-changelog.test.ts, scripts/build-changelog.ts.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Removed the launcher YouTube subtitle generation mode surface so YouTube playback now always runs the subtitle generation pipeline before starting mpv. The launcher no longer accepts youtubeSubgen.mode from shared config, CLI, or env normalization, and the old automatic/background loading path has been deleted from playback.
|
||||
|
||||
Updated mpv YouTube startup options to keep manual subtitle discovery enabled without requesting auto subtitles, and refreshed user-facing config/docs to describe a single YouTube subtitle generation flow. Added regression coverage for mode removal, config/template cleanup, and launcher ordering so YouTube subtitle work is confirmed to happen before mpv launch.
|
||||
|
||||
Verification: bun test launcher/config-domain-parsers.test.ts launcher/parse-args.test.ts launcher/mpv.test.ts launcher/main.test.ts src/config/config.test.ts; bun run test:config:src; bun run typecheck. Broader unrelated repo issues remain in launcher/aniskip-metadata.test.ts and existing formatting drift in launcher/youtube/orchestrator.ts plus scripts/build-changelog files.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: TASK-125
|
||||
title: Add native AI API key secret storage
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-08 07:25'
|
||||
labels:
|
||||
- ai
|
||||
- config
|
||||
- security
|
||||
dependencies: []
|
||||
references:
|
||||
- /Users/sudacode/projects/japanese/SubMiner/src/ai/client.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/core/services/anilist/anilist-token-store.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/core/services/jellyfin-token-store.ts
|
||||
- /Users/sudacode/projects/japanese/SubMiner/src/main.ts
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Store the shared AI provider API key using the app's native secret-storage pattern so users do not need to keep the OpenRouter key in config files or shell commands.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 Users can configure the shared AI provider without storing the API key in config.jsonc.
|
||||
- [ ] #2 The app persists and reloads the shared AI API key using encrypted native secret storage when available.
|
||||
- [ ] #3 Behavior is defined for existing ai.apiKey and ai.apiKeyCommand configs, including compatibility during migration.
|
||||
- [ ] #4 The feature has regression tests covering key resolution and storage behavior.
|
||||
- [ ] #5 User-facing configuration/docs are updated to describe the supported setup.
|
||||
<!-- AC:END -->
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
id: TASK-126
|
||||
title: >-
|
||||
Improve secondary subtitle readability with hover-only background and stronger
|
||||
text separation
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-08 07:35'
|
||||
updated_date: '2026-03-08 07:40'
|
||||
labels:
|
||||
- overlay
|
||||
- subtitles
|
||||
- ui
|
||||
dependencies: []
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Adjust overlay secondary subtitle styling so translation text stays readable on bright video backgrounds. Keep the dark background hidden by default in hover mode and show it only while hovered. Increase secondary subtitle weight to 600 and strengthen edge separation without changing primary subtitle styling.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Secondary subtitles render with stronger edge separation than today.
|
||||
- [x] #2 Secondary subtitle font weight defaults to 600.
|
||||
- [x] #3 When secondary subtitle mode is hover, the secondary background appears only while hovered.
|
||||
- [x] #4 Primary subtitle styling behavior remains unchanged.
|
||||
- [x] #5 Renderer tests cover the new secondary hover background behavior and default secondary style values.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Adjusted secondary subtitle defaults to use stronger shadowing, 600 font weight, and a translucent dark background. Routed secondary background/backdrop styling through CSS custom properties so hover mode can keep the background hidden until the secondary subtitle is actually hovered. Added renderer and config tests covering default values and hover-only background behavior.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Improved secondary subtitle readability by strengthening default text separation, increasing the default secondary weight to 600, and making the configured dark background appear only while hovered in secondary hover mode. Added config and renderer coverage for the new defaults and hover-aware style routing.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,83 @@
|
||||
---
|
||||
id: TASK-127
|
||||
title: Skip AniSkip lookup for YouTube and URL playback targets
|
||||
status: Done
|
||||
assignee:
|
||||
- '@codex'
|
||||
created_date: '2026-03-08 08:24'
|
||||
updated_date: '2026-03-08 10:12'
|
||||
labels:
|
||||
- bug
|
||||
- launcher
|
||||
- youtube
|
||||
dependencies: []
|
||||
references:
|
||||
- /Users/sudacode/projects/japanese/SubMiner/launcher/mpv.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/launcher/commands/playback-command.ts
|
||||
- /Users/sudacode/projects/japanese/SubMiner/launcher/mpv.test.ts
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Prevent launcher playback from attempting AniSkip metadata resolution when the user is playing a YouTube target or any URL target. AniSkip only works for local anime files, so URL-driven playback and YouTube subtitle-generation flows should bypass it entirely.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Launcher playback skips AniSkip metadata resolution for explicit URL targets, including YouTube URLs.
|
||||
- [x] #2 YouTube subtitle-generation playback does not invoke AniSkip lookup before mpv launch.
|
||||
- [x] #3 Automated launcher tests cover the URL/YouTube skip behavior.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Add a launcher mpv unit test that intercepts AniSkip resolution and proves URL/YouTube playback does not call it before spawning mpv.
|
||||
2. Run the focused launcher mpv test to confirm the new case fails or exposes the current gap.
|
||||
3. Patch launcher playback/AniSkip gating so URL and YouTube subtitle-generation paths always bypass AniSkip lookup.
|
||||
4. Re-run focused launcher tests and record the verification results in task notes.
|
||||
|
||||
5. Add a Lua plugin regression test covering overlay-start on URL playback so AniSkip never runs after auto-start.
|
||||
|
||||
6. Patch plugin/subminer/aniskip.lua to short-circuit all AniSkip lookup triggers for remote URL media paths.
|
||||
|
||||
7. Re-run plugin regression plus touched launcher checks and update the task summary with the plugin-side fix.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Added explicit AniSkip gating in launcher/mpv.ts via shouldResolveAniSkipMetadata(target, targetKind, preloadedSubtitles).
|
||||
|
||||
URL targets now always bypass AniSkip. File targets with preloaded subtitles also bypass AniSkip, covering YouTube subtitle-preload playback.
|
||||
|
||||
Added launcher/mpv.test.ts coverage for local-file vs URL vs preloaded-subtitle AniSkip gating.
|
||||
|
||||
Verification: bun test launcher/mpv.test.ts passed.
|
||||
|
||||
Verification: bun run typecheck passed.
|
||||
|
||||
Verification: bunx prettier --check launcher/mpv.ts launcher/mpv.test.ts passed.
|
||||
|
||||
Verification: bun run changelog:lint passed.
|
||||
|
||||
Verification: bun run test:launcher:unit:src remains blocked by unrelated existing failure in launcher/aniskip-metadata.test.ts (`resolveAniSkipMetadataForFile resolves MAL id and intro payload`: expected malId 1234, got null).
|
||||
|
||||
Added plugin regression in scripts/test-plugin-start-gate.lua for URL playback with auto-start/overlay-start; it now asserts no MAL or AniSkip curl requests occur.
|
||||
|
||||
Patched plugin/subminer/aniskip.lua to short-circuit AniSkip lookup for remote media paths (`scheme://...`), which covers YouTube URL playback inside the mpv plugin lifecycle.
|
||||
|
||||
Verification: lua scripts/test-plugin-start-gate.lua passed.
|
||||
|
||||
Verification: bun run test:plugin:src passed.
|
||||
|
||||
Verification: bun test launcher/mpv.test.ts passed after plugin-side fix.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Fixed AniSkip suppression end-to-end for URL playback. The launcher now skips AniSkip before mpv launch, and the mpv plugin now also refuses AniSkip lookups for remote URL media during file-loaded, overlay-start, or later refresh triggers. Added regression coverage in both launcher/mpv.test.ts and scripts/test-plugin-start-gate.lua, plus a changelog fragment. Wider `bun run test:launcher:unit:src` is still blocked by the unrelated existing launcher/aniskip-metadata.test.ts MAL-id failure.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: TASK-128
|
||||
title: >-
|
||||
Prevent AI subtitle fix from translating primary YouTube subtitles into the
|
||||
wrong language
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-08 09:02'
|
||||
updated_date: '2026-03-08 09:17'
|
||||
labels:
|
||||
- bug
|
||||
- youtube-subgen
|
||||
- ai
|
||||
dependencies: []
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
AI subtitle cleanup can preserve cue structure while changing subtitle language, causing primary Japanese subtitle files to come back in English. Add guards so AI-fixed subtitles preserve expected language and fall back to raw Whisper output when language drifts.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Primary AI subtitle fix rejects output that drifts away from the expected source language.
|
||||
- [x] #2 Rejected AI fixes fall back to the raw Whisper subtitle without corrupting published subtitle language.
|
||||
- [x] #3 Regression tests cover a primary Japanese subtitle batch being translated into English by the AI fixer.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Added a primary-language guard to AI subtitle fixing so Japanese source subtitles are rejected if the AI rewrites them into English while preserving SRT structure. The fixer now receives the expected source language from the YouTube orchestrator, and regression coverage verifies that language drift falls back to the raw Whisper subtitle path.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
id: TASK-129
|
||||
title: >-
|
||||
Split AI model and system prompt config between Anki and YouTube subtitle
|
||||
generation
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-08 09:40'
|
||||
updated_date: '2026-03-08 09:57'
|
||||
labels:
|
||||
- config
|
||||
- ai
|
||||
- anki
|
||||
- youtube-subgen
|
||||
dependencies: []
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
The current top-level shared AI config forces Anki translation and YouTube subtitle fixing to share the same model and system prompt, which caused subtitle-fix requests to inherit a translation prompt and translate Japanese primary subtitles into English. Refactor config so provider credentials stay shared while model and system prompt can be configured per feature.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Anki integration can use its own AI model and system prompt independently of YouTube subtitle generation.
|
||||
- [x] #2 YouTube subtitle generation can use its own AI model and system prompt independently of Anki integration.
|
||||
- [x] #3 Existing shared provider credentials remain reusable without duplicating API key/base URL config.
|
||||
- [x] #4 Config example, defaults, validation, and regression tests cover the new per-feature override shape.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Added per-feature AI model/systemPrompt overrides for Anki and YouTube subtitle generation while keeping shared provider transport settings reusable. Anki now accepts `ankiConnect.ai` object config with `enabled`, `model`, and `systemPrompt`; YouTube subtitle generation accepts `youtubeSubgen.ai` overrides and merges them over the shared AI provider config. Updated config resolution, launcher parsing, runtime wiring, hot-reload handling, example config, and regression coverage.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
id: TASK-130
|
||||
title: Keep background SubMiner alive after launcher-managed mpv exits
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-08 10:08'
|
||||
updated_date: '2026-03-08 11:00'
|
||||
labels:
|
||||
- bug
|
||||
- launcher
|
||||
- mpv
|
||||
- overlay
|
||||
dependencies: []
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
The launcher currently tears down the running SubMiner background process when a launcher-managed mpv session exits. Background SubMiner should remain alive so a later mpv instance can reconnect and request the overlay without restarting the app.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Closing a launcher-managed mpv session does not send `--stop` to the running SubMiner background process.
|
||||
- [x] #2 Closing a launcher-managed mpv session does not SIGTERM the tracked SubMiner process just because mpv exited.
|
||||
- [x] #3 Launcher cleanup still terminates mpv and launcher-owned helper children without regressing existing overlay start behavior.
|
||||
- [x] #4 Automated tests cover the no-stop-on-mpv-exit behavior.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Add a launcher regression test that proves mpv exit no longer triggers SubMiner `--stop` or launcher SIGTERM of the tracked overlay process.
|
||||
2. Update launcher teardown so normal mpv-session cleanup only stops mpv/helper children and preserves the background SubMiner process for future reconnects.
|
||||
3. Run the focused launcher tests and smoke coverage for the affected behavior, then record results in the task.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Split launcher cleanup so normal mpv-session shutdown no longer sends `--stop` to SubMiner or SIGTERM to the tracked overlay process. Added `cleanupPlaybackSession()` for mpv/helper-child cleanup only, and switched playback finalization to use it.
|
||||
|
||||
Updated launcher smoke coverage to assert the background app stays alive after mpv exits, and added a focused unit regression for the new cleanup path.
|
||||
|
||||
Validation: `bun test launcher/mpv.test.ts launcher/smoke.e2e.test.ts` passed; `bun run typecheck` passed. `bun run test:launcher:unit:src` still reports an unrelated pre-existing failure in `launcher/aniskip-metadata.test.ts`.
|
||||
|
||||
Added changelog fragment `changes/task-130.md` for the launcher fix and verified it with `bun run changelog:lint`.
|
||||
|
||||
User verified the bug still reproduces when closing playback with `q`. Root cause narrowed further: the mpv plugin `plugin/subminer/lifecycle.lua` calls `process.stop_overlay()` on mpv `shutdown`, which still sends SubMiner `--stop` even after launcher cleanup was fixed.
|
||||
|
||||
Patched the remaining stop path in `plugin/subminer/lifecycle.lua`: mpv `shutdown` no longer calls `process.stop_overlay()`. Pressing mpv `q` should now preserve the background app and only tear down the mpv session.
|
||||
|
||||
Validation update: `lua scripts/test-plugin-start-gate.lua` passed after adding a shutdown regression, and `bun test launcher/mpv.test.ts launcher/smoke.e2e.test.ts` still passed.
|
||||
|
||||
Fixed a second-instance reconnect bug in `src/core/services/cli-command.ts`: `--start` on an already-initialized running instance now still updates the MPV socket path and reconnects the MPV client instead of treating the command as a no-op. This keeps the already-warmed background app reusable for later mpv launches.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Kept the background SubMiner process reusable across both mpv shutdown and later reconnects. The first fix separated launcher playback cleanup from full app shutdown. The second fix removed the mpv plugin `shutdown` stop call so default mpv `q` no longer sends SubMiner `--stop`. The third fix corrected second-instance CLI handling so `--start` on an already-running, already-initialized instance still reconnects MPV instead of being ignored.
|
||||
|
||||
Net effect: background SubMiner can stay alive, keep its warm state, and reconnect to later mpv instances without rerunning startup/warmup work in a fresh app instance.
|
||||
|
||||
Coverage now includes: launcher playback cleanup (`launcher/mpv.test.ts`), launcher smoke reconnect/keep-alive flow (`launcher/smoke.e2e.test.ts`), mpv plugin shutdown preservation (`scripts/test-plugin-start-gate.lua`), and second-instance start/reconnect behavior (`src/core/services/cli-command.test.ts`).
|
||||
|
||||
Tests run:
|
||||
- `bun test src/core/services/cli-command.test.ts launcher/mpv.test.ts launcher/smoke.e2e.test.ts`
|
||||
- `lua scripts/test-plugin-start-gate.lua`
|
||||
- `bun run typecheck`
|
||||
- `bun run changelog:lint`
|
||||
|
||||
Note: the broader `bun run test:launcher:unit:src` lane still has an unrelated pre-existing failure in `launcher/aniskip-metadata.test.ts`.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
id: TASK-131
|
||||
title: Avoid duplicate tokenization warmup after background startup
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-08 10:12'
|
||||
updated_date: '2026-03-08 12:00'
|
||||
labels:
|
||||
- bug
|
||||
dependencies: []
|
||||
references:
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/main/runtime/composers/mpv-runtime-composer.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/main/runtime/startup-warmups.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/main/runtime/composers/mpv-runtime-composer.test.ts
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
When SubMiner is already running in the background and mpv is launched from the launcher or mpv plugin, the live app should reuse startup tokenization warmup state instead of re-entering the Yomitan/tokenization/annotation warmup path on first overlay use.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Background startup tokenization warmup is recorded in the runtime state used by later mpv/tokenization flows.
|
||||
- [x] #2 Launching mpv from the launcher or plugin against an already-running background app does not re-run duplicate Yomitan/tokenization annotation warmup work in the live process.
|
||||
- [x] #3 Regression tests cover the warmed-background path and protect against re-entering duplicate warmup work.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Add a regression test covering the case where background startup warmups already completed and a later tokenize call must not re-enter Yomitan/MeCab/dictionary warmups.
|
||||
2. Update mpv tokenization warmup composition so startup background warmups and on-demand tokenization share the same completion state.
|
||||
3. Run the focused composer/runtime tests and update acceptance criteria/notes with results.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Root-cause hypothesis: startup background warmups and on-demand tokenization warmups use separate state, so later mpv launch can re-enter warmup bookkeeping even though background startup already warmed dependencies.
|
||||
|
||||
Implemented shared warmup state between startup background warmups and on-demand tokenization warmups by forwarding scheduled Yomitan/tokenization promises into the mpv runtime composer. Added regression coverage for the warmed-background path. Verified with `bun run test:fast` plus focused composer/startup warmup tests.
|
||||
|
||||
Follow-up root cause from live retest: second mpv open could still pause on the startup gate because the runtime only treated full background tokenization warmup completion as reusable readiness. In practice, first-file tokenization could already be ready while slower dictionary prewarm work was still finishing, so reopening a video waited on duplicate warmup completion even though annotations were already usable.
|
||||
|
||||
Adjusted `src/main/runtime/composers/mpv-runtime-composer.ts` so autoplay reuse keys off a separate playback-ready latch. The latch flips true either when background warmups fully cover tokenization or when `onTokenizationReady` fires for a real subtitle line. `src/main.ts` already uses `isTokenizationWarmupReady()` to fast-signal `subminer-autoplay-ready` on a fresh media-path change, so reopened videos can now resume immediately once tokenization has succeeded once in the persistent app.
|
||||
|
||||
Validation update: `bun test src/core/services/cli-command.test.ts src/main/runtime/mpv-main-event-actions.test.ts src/main/runtime/composers/mpv-runtime-composer.test.ts launcher/mpv.test.ts launcher/smoke.e2e.test.ts` passed, `lua scripts/test-plugin-start-gate.lua` passed, and `bun run typecheck` passed.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Background startup tokenization warmups now feed the same in-memory warmup state used by later mpv tokenization. When the app is already running and warmed in the background, launcher/plugin-driven mpv startup reuses that state instead of re-entering Yomitan/tokenization annotation warmups. Added a regression test for the warmed-background path and verified with `bun run test:fast`.
|
||||
|
||||
A later follow-up fixed the remaining second-open delay: autoplay reuse no longer waits for the entire background dictionary warmup pipeline to finish. After the persistent app has produced one tokenization-ready event, later mpv reconnects reuse that readiness immediately, so reopening the same or another video does not pause again on duplicate warmup bookkeeping.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
id: TASK-132
|
||||
title: Gate macOS overlay shortcuts to the focused mpv window
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-08 18:24'
|
||||
updated_date: '2026-03-08 18:55'
|
||||
labels:
|
||||
- bug
|
||||
- macos
|
||||
- shortcuts
|
||||
dependencies: []
|
||||
references:
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/core/services/overlay-shortcut.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/window-trackers/macos-tracker.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/scripts/get-mpv-window-macos.swift
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Fix the macOS shortcut handling so SubMiner overlay keybinds do not intercept system or other-app shortcuts while SubMiner is in the background. Overlay shortcuts should only be active while the tracked mpv window is present and focused, and should stop grabbing keyboard input when mpv is not the frontmost window.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 On macOS, overlay shortcuts do not trigger while mpv is not the focused/frontmost window.
|
||||
- [x] #2 On macOS, overlay shortcuts remain available while the tracked mpv window is open and focused.
|
||||
- [x] #3 Existing non-macOS shortcut behavior is unchanged.
|
||||
- [x] #4 Automated tests cover the macOS focus-gating behavior and guard against background shortcut interception.
|
||||
- [x] #5 Any user-facing docs/config notes affected by the behavior change are updated in the same task if needed.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Add a failing macOS-focused shortcut lifecycle test that proves overlay shortcuts stay inactive when the tracked mpv window exists but is not frontmost, and activate when that tracked window becomes frontmost.
|
||||
2. Add a failing tracker/helper test that covers the focused/frontmost signal parsed from the macOS helper output.
|
||||
3. Extend the macOS helper/tracker contract to surface both geometry and focused/frontmost state for the tracked mpv window.
|
||||
4. Wire overlay shortcut activation to require both overlay runtime initialization and tracked-mpv focus on macOS, while leaving non-macOS behavior unchanged.
|
||||
5. Re-run the targeted shortcut/tracker tests, then the broader related shortcut/runtime suite, and update task notes/acceptance criteria based on results.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Added a macOS-specific shortcut activation predicate so global overlay shortcuts now require both overlay runtime readiness and a focused tracked mpv window; non-macOS behavior still keys off runtime readiness only.
|
||||
|
||||
Extended the base window tracker with optional focus-state callbacks/getters and wired initializeOverlayRuntime to re-sync overlay shortcuts whenever tracker focus changes.
|
||||
|
||||
Updated the macOS helper/tracker contract to return geometry plus frontmost/focused state for the tracked mpv process and added parser coverage for focused and unfocused output.
|
||||
|
||||
Verified with `bun x tsc -p tsconfig.json --noEmit`, targeted shortcut/tracker tests, and `bun run test:core:src` (439 passing).
|
||||
|
||||
No user-facing config or documentation surface changed, so no docs update was required for this fix.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Fixed the macOS background shortcut interception bug by gating SubMiner's global overlay shortcuts on tracked mpv focus instead of overlay-runtime initialization alone. The macOS window helper now reports whether the tracked mpv process is frontmost, the tracker exposes focus change callbacks, and overlay shortcut synchronization re-runs when that focus state flips so `Ctrl+C`/`Ctrl+V` and similar shortcuts are no longer captured while mpv is in the background.
|
||||
|
||||
The change keeps existing non-macOS shortcut behavior unchanged. Added regression coverage for the activation decision, tracker focus-change re-sync, and macOS helper output parsing. Verification: `bun x tsc -p tsconfig.json --noEmit`, targeted shortcut/tracker tests, and `bun run test:core:src` (439 passing).
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
id: TASK-133
|
||||
title: Improve AniList character dictionary parity with upstream guide
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-08 21:06'
|
||||
updated_date: '2026-03-08 21:35'
|
||||
labels:
|
||||
- dictionary
|
||||
- anilist
|
||||
- planning
|
||||
dependencies: []
|
||||
references:
|
||||
- >-
|
||||
https://github.com/bee-san/Japanese_Character_Name_Dictionary/blob/main/docs/agents_read_me.md
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/main/character-dictionary-runtime.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/main/character-dictionary-runtime.test.ts
|
||||
documentation:
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/docs/plans/2026-03-08-anilist-character-dictionary-parity-design.md
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/docs/plans/2026-03-08-anilist-character-dictionary-parity.md
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Plan and implement guide-faithful parity improvements for the AniList character dictionary flow inside SubMiner's current single-media generation path. Scope includes AniList first/last name hints, hint-aware reading generation for kanji/native names, expanded honorific coverage, 160x200 JPEG thumbnail handling, and AniList 429 retry/backoff behavior.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 AniList character queries include first/last name fields and preserve them through runtime data models.
|
||||
- [ ] #2 Dictionary generation uses hint-aware name splitting and reading generation for kanji and mixed native names, not only kana-only readings.
|
||||
- [ ] #3 Honorific generation is expanded substantially toward upstream coverage and is covered by regression tests.
|
||||
- [ ] #4 Character and voice-actor images are resized or re-encoded to bounded JPEG thumbnails with fallback behavior.
|
||||
- [ ] #5 AniList requests handle 429 responses with bounded exponential backoff and tests cover retry behavior.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Approved design and implementation plan captured on 2026-03-08. Scope stays within current single-media AniList dictionary flow; excludes username-driven CURRENT-list fetching and Yomitan auto-update schema work.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
id: TASK-134
|
||||
title: Harden Windows release signing against transient SignPath failures
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-09 00:00'
|
||||
updated_date: '2026-03-08 20:23'
|
||||
labels:
|
||||
- ci
|
||||
- release
|
||||
- windows
|
||||
- signing
|
||||
dependencies: []
|
||||
references:
|
||||
- .github/workflows/release.yml
|
||||
- package.json
|
||||
- src/release-workflow.test.ts
|
||||
- https://github.com/ksyasuda/SubMiner/actions/runs/22836585479
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
The tag-driven Release workflow currently fails the Windows lane if the SignPath connector returns transient 502 errors during submission, and the tagged build scripts also allow electron-builder to implicitly publish unsigned artifacts before the final release job runs. Harden the workflow so transient SignPath outages get bounded retries and release packaging never auto-publishes unsigned assets.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 Windows release signing retries transient SignPath submission failures within the release workflow before failing the job.
|
||||
- [ ] #2 Release packaging scripts disable electron-builder implicit publish so build jobs do not upload unsigned assets on tag builds.
|
||||
- [ ] #3 Regression coverage fails if SignPath retry scaffolding or publish suppression is removed.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Add a regression test for the release workflow/package script shape covering SignPath retries and `--publish never`.
|
||||
2. Patch the Windows release job to retry SignPath submission a bounded number of times and still fail hard if every attempt fails.
|
||||
3. Update tagged package build scripts to disable implicit electron-builder publishing during release builds.
|
||||
4. Run targeted release-workflow verification and capture any remaining manual release cleanup needed for `v0.5.0`.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
The failed Windows signing step in GitHub Actions run `22836585479` was not caused by missing secrets or an artifact-shape mismatch. The SignPath GitHub action retried repeated `502` responses from the SignPath connector for several minutes and then failed the job.
|
||||
|
||||
Hardened `.github/workflows/release.yml` by replacing the single SignPath submission with three bounded attempts. The second and third submissions only run if the previous attempt failed, and the job now fails with an explicit rerun message only after all three attempts fail. Signed-artifact upload is keyed to the successful attempt so the release job still consumes the normal `windows` artifact name.
|
||||
|
||||
Also fixed a separate release regression exposed by the same run: `electron-builder` was implicitly publishing unsigned release assets during tag builds because the packaging scripts did not set `--publish never` and the workflow injected `GH_TOKEN` into build jobs. Updated the relevant package scripts to pass `--publish never`, removed `GH_TOKEN` from the packaging jobs, and made the final publish step force `--draft=false` when editing an existing tag release so previously-created draft releases get published.
|
||||
|
||||
Verification: `bun test src/release-workflow.test.ts`, `bun run typecheck`, and `bun run test:fast` all passed locally after restoring the missing local `libsql` install with `bun install --frozen-lockfile`.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Windows release signing is now resilient to transient SignPath connector outages. The release workflow retries the SignPath submission up to three times before failing, and only uploads the signed Windows artifact from the attempt that succeeded.
|
||||
|
||||
Release packaging also no longer auto-publishes unsigned assets on tag builds. The `electron-builder` scripts now force `--publish never`, the build jobs no longer pass `GH_TOKEN` into packaging steps, and the final GitHub release publish step explicitly clears draft state when updating an existing tag release.
|
||||
|
||||
Validation: `bun test src/release-workflow.test.ts`, `bun run typecheck`, `bun run test:fast`.
|
||||
Manual follow-up for the failed `v0.5.0` release: rerun the `Release` workflow after merging/pushing this fix, then clean up the stray draft/untagged release assets created by the failed run if they remain.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
id: TASK-135
|
||||
title: Cut patch release v0.5.1 for Windows signing fix
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-08 20:24'
|
||||
updated_date: '2026-03-08 20:28'
|
||||
labels:
|
||||
- release
|
||||
- patch
|
||||
dependencies:
|
||||
- TASK-134
|
||||
references:
|
||||
- package.json
|
||||
- CHANGELOG.md
|
||||
- release/release-notes.md
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Publish a patch release from the workflow-signing fix on `main` by bumping the app version, generating the committed changelog artifacts for the new version, and pushing a new `v0.5.1` tag instead of rewriting the failed `v0.5.0` tag.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 Repository version metadata is updated to `0.5.1`.
|
||||
- [ ] #2 `CHANGELOG.md` and `release/release-notes.md` contain the committed `v0.5.1` section and released fragments are removed.
|
||||
- [ ] #3 New `v0.5.1` commit and tag are pushed to `origin`.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Bump the package version to `0.5.1`.
|
||||
2. Run the changelog builder so `CHANGELOG.md`/`release-notes.md` match the release workflow contract.
|
||||
3. Run the relevant verification commands.
|
||||
4. Commit the release-prep changes, create `v0.5.1`, and push both commit and tag.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Bumped `package.json` from `0.5.0` to `0.5.1`, then ran `bun run changelog:build` so the committed release artifacts match the release workflow contract. That prepended the `v0.5.1` section to `CHANGELOG.md`, regenerated `release/release-notes.md`, and removed the consumed changelog fragments from `changes/`.
|
||||
|
||||
Verification before tagging: `bun run changelog:lint`, `bun run changelog:check --version 0.5.1`, `bun run typecheck`, and `bun run test:fast`.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Prepared patch release `v0.5.1` from the signing-workflow fix on `main` instead of rewriting the failed `v0.5.0` tag. Repository version metadata, changelog, and committed release notes are all aligned with the new release tag, and the consumed changelog fragments were removed.
|
||||
|
||||
Validation: `bun run changelog:lint`, `bun run changelog:check --version 0.5.1`, `bun run typecheck`, `bun run test:fast`.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,61 @@
|
||||
---
|
||||
id: TASK-136
|
||||
title: Pin SignPath artifact configuration in release workflow
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-08 20:41'
|
||||
updated_date: '2026-03-08 20:58'
|
||||
labels:
|
||||
- ci
|
||||
- release
|
||||
- windows
|
||||
- signing
|
||||
dependencies:
|
||||
- TASK-134
|
||||
references:
|
||||
- .github/workflows/release.yml
|
||||
- build/signpath-windows-artifact-config.xml
|
||||
- src/release-workflow.test.ts
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
The Windows release workflow currently relies on the default SignPath artifact configuration configured in the SignPath UI. Pin the workflow to an explicit artifact-configuration slug so the checked-in signing configuration and CI behavior stay deterministic across future SignPath project changes.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 The Windows release workflow validates a dedicated SignPath artifact-configuration secret/input.
|
||||
- [ ] #2 Every SignPath submission attempt passes `artifact-configuration-slug`.
|
||||
- [ ] #3 Regression coverage fails if the explicit SignPath artifact-configuration binding is removed.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Add a failing workflow regression test for the explicit SignPath artifact-configuration slug.
|
||||
2. Patch the Windows signing secret validation and SignPath action inputs to require the slug.
|
||||
3. Run targeted release-workflow verification plus the standard fast lane.
|
||||
4. Cut a new patch release so the tag-triggered release workflow runs with the pinned SignPath configuration.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Added regression coverage in `src/release-workflow.test.ts` for an explicit SignPath artifact-configuration slug so the release workflow test now fails if the slug validation or action input is removed.
|
||||
|
||||
Patched `.github/workflows/release.yml` so Windows signing now requires `SIGNPATH_ARTIFACT_CONFIGURATION_SLUG` during secret validation and passes `artifact-configuration-slug: ${{ secrets.SIGNPATH_ARTIFACT_CONFIGURATION_SLUG }}` on every SignPath submission attempt.
|
||||
|
||||
Verification: `bun test src/release-workflow.test.ts`, `bun run typecheck`, `bun run test:fast`.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
The release workflow is now pinned to an explicit SignPath artifact configuration instead of relying on whichever SignPath artifact config is marked default in the UI. Windows signing secret validation fails fast if `SIGNPATH_ARTIFACT_CONFIGURATION_SLUG` is missing, and every SignPath submission attempt now includes the pinned slug.
|
||||
|
||||
Validation: `bun test src/release-workflow.test.ts`, `bun run typecheck`, `bun run test:fast`.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
id: TASK-137
|
||||
title: Cut patch release v0.5.2 for SignPath artifact config pinning
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-08 20:44'
|
||||
updated_date: '2026-03-08 20:58'
|
||||
labels:
|
||||
- release
|
||||
- patch
|
||||
dependencies:
|
||||
- TASK-136
|
||||
references:
|
||||
- package.json
|
||||
- CHANGELOG.md
|
||||
- release/release-notes.md
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Publish a patch release from the SignPath artifact-configuration pinning change by bumping the app version, generating the committed changelog artifacts for the new version, and pushing a new `v0.5.2` tag.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [ ] #1 Repository version metadata is updated to `0.5.2`.
|
||||
- [ ] #2 `CHANGELOG.md` and `release/release-notes.md` contain the committed `v0.5.2` section and consumed fragments are removed.
|
||||
- [ ] #3 New `v0.5.2` commit and tag are pushed to `origin`.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Add the release fragment for the SignPath configuration pinning change.
|
||||
2. Bump `package.json` to `0.5.2` and run the changelog builder.
|
||||
3. Run changelog/typecheck/test verification.
|
||||
4. Commit the release-prep change set, create `v0.5.2`, and push commit plus tag.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Bumped `package.json` from `0.5.1` to `0.5.2`, ran `bun run changelog:build`, and committed the generated release artifacts. That prepended the `v0.5.2` section to `CHANGELOG.md`, regenerated `release/release-notes.md`, and removed the consumed `changes/signpath-artifact-config-pin.md` fragment.
|
||||
|
||||
Verification before tagging: `bun run changelog:lint`, `bun run changelog:check --version 0.5.2`, `bun run typecheck`, and `bun run test:fast`.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Prepared patch release `v0.5.2` so the explicit SignPath artifact-configuration pin ships on a fresh release tag. Version metadata, committed changelog artifacts, and release notes are aligned with the new patch version.
|
||||
|
||||
Validation: `bun run changelog:lint`, `bun run changelog:check --version 0.5.2`, `bun run typecheck`, `bun run test:fast`.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -6,7 +6,7 @@ title: >-
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-28 02:38'
|
||||
updated_date: '2026-02-28 22:36'
|
||||
updated_date: '2026-03-04 13:55'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
@@ -49,4 +49,10 @@ Risk/impact context:
|
||||
|
||||
Completed implementation in branch working tree; ready to merge once local changes are committed and test gate passes.
|
||||
|
||||
Follow-up fix (2026-03-04):
|
||||
|
||||
- Updated bundled Yomitan server-sync behavior to target `profileCurrent` instead of hardcoded `profiles[0]`.
|
||||
- Added proxy-mode force override so bundled Yomitan always points at SubMiner proxy URL when `ankiConnect.proxy.enabled=true`; this ensures mined cards pass through proxy and trigger auto-enrichment.
|
||||
- Added regression tests for blocked existing-server case and force-override injection path.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
|
||||
@@ -4,7 +4,7 @@ title: 'Subtitle hover: auto-pause playback with config toggle'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-28 22:43'
|
||||
updated_date: '2026-02-28 22:43'
|
||||
updated_date: '2026-03-04 12:07'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
@@ -18,6 +18,7 @@ ordinal: 8000
|
||||
Add a user-facing subtitle config option to pause mpv playback when the cursor hovers subtitle text and resume playback when the cursor leaves.
|
||||
|
||||
Scope:
|
||||
|
||||
- New config key: `subtitleStyle.autoPauseVideoOnHover`.
|
||||
- Default should be enabled.
|
||||
- Hover pause/resume must not unpause if playback was already paused before hover.
|
||||
@@ -42,4 +43,11 @@ Scope:
|
||||
|
||||
Implemented `subtitleStyle.autoPauseVideoOnHover` with default `true`, wired through config defaults/resolution/types, renderer state/style, and mouse hover handlers. Added playback pause-state IPC (`getPlaybackPaused`) to avoid false resume when media was already paused. Added renderer hover behavior tests (including race/cancel case) and config/resolve tests. Updated config examples and docs (`README`, usage, shortcuts, mining workflow, configuration) to document default hover pause/resume behavior and disable path.
|
||||
|
||||
Follow-up adjustments (2026-03-04):
|
||||
|
||||
- Hover pause now resumes immediately when leaving subtitle text (no Yomitan-popup hover retention).
|
||||
- Added `subtitleStyle.autoPauseVideoOnYomitanPopup` (default `false`) to optionally keep playback paused while Yomitan popup is open, with auto-resume on close only when SubMiner initiated the popup pause.
|
||||
- Yomitan popup control keybinds added while popup is open: `J/K` scroll, `M` mine, `P` audio play, `[` previous audio variant, `]` next audio variant (within selected source).
|
||||
- Extension copy drift detection widened so popup runtime changes are reliably re-copied on launch (`popup.js`, `popup-main.js`, `display.js`, `display-audio.js`).
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
|
||||
@@ -18,6 +18,7 @@ ordinal: 9000
|
||||
Add startup gating behavior for wrapper + mpv plugin flow so playback starts paused when visible overlay auto-start is enabled, then auto-resumes only after subtitle tokenization is ready.
|
||||
|
||||
Scope:
|
||||
|
||||
- Plugin option `auto_start_pause_until_ready` (default `yes`).
|
||||
- Launcher reads plugin runtime config and starts mpv paused when `auto_start=yes`, `auto_start_visible_overlay=yes`, and `auto_start_pause_until_ready=yes`.
|
||||
- Main process signals readiness via mpv script message after tokenized subtitle delivery.
|
||||
@@ -43,6 +44,7 @@ Scope:
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Implemented startup pause gate across launcher/plugin/main runtime:
|
||||
|
||||
- Added plugin runtime config parsing in launcher (`auto_start`, `auto_start_visible_overlay`, `auto_start_pause_until_ready`) and mpv start-paused behavior for eligible runs.
|
||||
- Added plugin auto-play gate state, timeout fallback, and readiness release via `subminer-autoplay-ready` script message.
|
||||
- Added main-process readiness signaling after tokenization delivery, including unpause fallback command path.
|
||||
|
||||
@@ -18,10 +18,12 @@ ordinal: 10000
|
||||
Fix Jimaku modal UX so selecting a subtitle file closes the modal automatically once subtitle download+load succeeds.
|
||||
|
||||
Current behavior:
|
||||
|
||||
- Subtitle file downloads and loads into mpv.
|
||||
- Jimaku modal remains open until manual close.
|
||||
|
||||
Expected behavior:
|
||||
|
||||
- On successful `jimakuDownloadFile` result, close modal immediately.
|
||||
- Keep error behavior unchanged (stay open + show error).
|
||||
|
||||
|
||||
@@ -18,11 +18,13 @@ ordinal: 11000
|
||||
When user selects a Jimaku subtitle, save subtitle with filename derived from currently playing media filename instead of Jimaku release filename.
|
||||
|
||||
Example:
|
||||
|
||||
- Current media: `anime.mkv`
|
||||
- Downloaded subtitle extension: `.srt`
|
||||
- Saved subtitle path: `anime.ja.srt`
|
||||
|
||||
Scope:
|
||||
|
||||
- Apply in Jimaku download IPC path before writing file.
|
||||
- Preserve collision-avoidance behavior (suffix with jimaku entry id/counter when target exists).
|
||||
- Keep mpv load flow unchanged except using renamed path.
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
id: TASK-81
|
||||
title: 'Tokenization performance: disable Yomitan MeCab parser, gate local MeCab init, and add persistent MeCab process'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-02 07:44'
|
||||
updated_date: '2026-03-02 20:44'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: high
|
||||
ordinal: 9001
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Reduce subtitle annotation latency by:
|
||||
|
||||
- disabling Yomitan-side MeCab parser requests (`useMecabParser=false`);
|
||||
- initializing local MeCab only when POS-dependent annotations are enabled (N+1 / JLPT / frequency);
|
||||
- replacing per-line local MeCab process spawning with a persistent parser process that auto-shuts down after idle time and restarts on demand.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Yomitan parse requests disable MeCab parser path.
|
||||
- [x] #2 MeCab warmup/init is skipped when all POS-dependent annotation toggles are off.
|
||||
- [x] #3 Local MeCab tokenizer uses persistent process across subtitle lines.
|
||||
- [x] #4 Persistent MeCab process auto-shuts down after idle timeout and restarts on next tokenize activity.
|
||||
- [x] #5 Tests cover parser flag, warmup gating, and persistent MeCab lifecycle behavior.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Implemented tokenizer latency optimizations:
|
||||
|
||||
- switched Yomitan parse requests to `useMecabParser: false`;
|
||||
- added annotation-aware MeCab initialization gating in runtime warmup flow;
|
||||
- added persistent local MeCab process (default idle shutdown: 30s) with queued requests, retry-on-process-end, idle auto-shutdown, and automatic restart on new work;
|
||||
- added regression tests for Yomitan parse flag, MeCab warmup gating, and persistent/idle lifecycle behavior;
|
||||
- fixed tokenization warmup gate so first-use warmup completion is sticky (`tokenizationWarmupCompleted`) and sequential `tokenizeSubtitle` calls no longer re-run Yomitan/dictionary warmup path;
|
||||
- added regression coverage in `src/main/runtime/composers/mpv-runtime-composer.test.ts` for sequential tokenize calls (`warmup` side effects run once);
|
||||
- post-review critical fix: treat Yomitan default-profile Anki server sync `no-change` as successful check, so `lastSyncedYomitanAnkiServer` is cached and expensive sync checks do not repeat on every subtitle line;
|
||||
- added regression assertion in `src/core/services/tokenizer/yomitan-parser-runtime.test.ts` for `updated: false` path returning sync success;
|
||||
- post-review performance fix: refactored POS enrichment to pre-index MeCab tokens by surface plus character-position overlap index, replacing repeated active-candidate filtering/full-scan behavior with direct overlap candidate lookup per token;
|
||||
- added regression tests in `src/core/services/tokenizer/parser-enrichment-stage.test.ts` for repeated distant-token scan access and repeated active-candidate filter scans; both fail on scan-based behavior and pass with indexed lookup;
|
||||
- post-review startup fix: moved JLPT/frequency dictionary initialization from synchronous FS APIs to async `fs/promises` path inspection/read and cooperative chunked entry processing to reduce main-thread stall risk during cold start;
|
||||
- post-review first-line latency fix: decoupled tokenization warmup gating so first `tokenizeSubtitle` only waits on Yomitan extension readiness, while MeCab check + dictionary prewarm continue in parallel background warmups;
|
||||
- validated with targeted tests and `tsc --noEmit`.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
id: TASK-82
|
||||
title: 'Subtitle frequency highlighting: fix noisy Yomitan readings and restore known/N+1 color priority'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-02 20:10'
|
||||
updated_date: '2026-03-02 01:44'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: high
|
||||
ordinal: 9002
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Address frequency-highlighting regressions:
|
||||
|
||||
- tokens like `断じて` missed rank assignment when Yomitan merged-token reading was truncated/noisy;
|
||||
- known/N+1 tokens were incorrectly colored by frequency color instead of known/N+1 color.
|
||||
|
||||
Expected behavior:
|
||||
|
||||
- known/N+1 color always wins;
|
||||
- if token is frequent and within `topX`, frequency rank label can still appear on hover/metadata.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Frequency lookup succeeds for noisy/truncated merged-token readings via robust fallback behavior.
|
||||
- [x] #2 Merged-token reading normalization restores missing kana suffixes where safe (`headword === surface` path).
|
||||
- [x] #3 Known/N+1 tokens keep known/N+1 color classes; frequency color class does not override them.
|
||||
- [x] #4 Frequency rank hover label remains available for in-range frequent tokens, including known/N+1.
|
||||
- [x] #5 Regression tests added for tokenizer and renderer behavior.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Implemented and validated:
|
||||
|
||||
- tokenizer now normalizes selected Yomitan merged-token readings by appending missing trailing kana suffixes when safe (`headword === surface`);
|
||||
- frequency lookup now does lazy fallback: requests `{term, reading}` first, and only requests `{term, reading: null}` for misses;
|
||||
- this removes eager `(term, null)` payload inflation on medium-frequency lines and reduces extension RPC payload/load;
|
||||
- renderer restored known/N+1 color priority over frequency class coloring;
|
||||
- frequency rank label display remains available for frequent known/N+1 tokens;
|
||||
- added regression tests covering noisy-reading fallback, lazy fallback-query behavior, and renderer class/label precedence.
|
||||
|
||||
Related commits:
|
||||
|
||||
- `17a417e` (`fix(subtitle): improve frequency highlight reliability`)
|
||||
- `79f37f3` (`fix(subtitle): prioritize known and n+1 colors over frequency`)
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,53 @@
|
||||
---
|
||||
id: TASK-83
|
||||
title: 'Jellyfin subtitle delay: shift to adjacent cue without seek jumps'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-02 00:06'
|
||||
updated_date: '2026-03-02 00:06'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: high
|
||||
ordinal: 9003
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Add keybinding-friendly special commands that shift `sub-delay` to align current subtitle start with next/previous cue start, without `sub-seek` probing (avoid playback jump).
|
||||
|
||||
Scope:
|
||||
|
||||
- add special commands for next/previous line alignment;
|
||||
- compute delta from active subtitle cue timeline (external subtitle file/URL, including Jellyfin-delivered URLs);
|
||||
- apply `add sub-delay <delta>` and show OSD value;
|
||||
- keep existing proxy OSD behavior for direct `sub-delay` keybinding commands.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 New special commands exist for subtitle-delay shift to next/previous cue boundary.
|
||||
- [x] #2 Shift logic parses active external subtitle source timings (SRT/VTT/ASS) and computes delta from current `sub-start`.
|
||||
- [x] #3 Runtime applies delay shift without `sub-seek` and shows OSD feedback.
|
||||
- [x] #4 Direct `sub-delay` proxy commands also show OSD current value.
|
||||
- [x] #5 Tests added for cue parsing/shift behavior and IPC dispatch wiring.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Implemented no-jump subtitle-delay alignment commands:
|
||||
|
||||
- added `__sub-delay-next-line` and `__sub-delay-prev-line` special commands;
|
||||
- added `createShiftSubtitleDelayToAdjacentCueHandler` to parse cue start times from active external subtitle source and apply `add sub-delay` delta from current `sub-start`;
|
||||
- wired command handling through IPC runtime deps into main runtime;
|
||||
- retained/extended OSD proxy feedback for `sub-delay` keybindings;
|
||||
- updated configuration docs and added regression tests for subtitle-delay shift and IPC command routing.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,37 @@
|
||||
---
|
||||
id: TASK-84
|
||||
title: 'Docs Plausible endpoint uses /api/event path'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-03 00:00'
|
||||
updated_date: '2026-03-03 00:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 12000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Fix VitePress docs Plausible tracker config to post to hosted worker API event endpoint instead of worker root URL.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Docs theme Plausible `endpoint` points to `https://worker.subminer.moe/api/event`.
|
||||
- [x] #2 Plausible docs test asserts `/api/event` endpoint path.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Updated docs Plausible tracker endpoint to `https://worker.subminer.moe/api/event` and updated regression test expectation accordingly.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
id: TASK-84
|
||||
title: Migrate AniSkip metadata+lookup orchestration to launcher/Electron
|
||||
status: Done
|
||||
assignee:
|
||||
- Codex
|
||||
created_date: '2026-03-03 08:31'
|
||||
updated_date: '2026-03-03 08:35'
|
||||
labels:
|
||||
- enhancement
|
||||
- aniskip
|
||||
- launcher
|
||||
- mpv-plugin
|
||||
dependencies: []
|
||||
references:
|
||||
- launcher/aniskip-metadata.ts
|
||||
- launcher/mpv.ts
|
||||
- plugin/subminer/aniskip.lua
|
||||
- plugin/subminer/options.lua
|
||||
- plugin/subminer/state.lua
|
||||
- plugin/subminer/lifecycle.lua
|
||||
- plugin/subminer/messages.lua
|
||||
- plugin/subminer.conf
|
||||
- launcher/aniskip-metadata.test.ts
|
||||
documentation:
|
||||
- docs/mpv-plugin.md
|
||||
- launcher/aniskip-metadata.ts
|
||||
- plugin/subminer/aniskip.lua
|
||||
- docs/architecture.md
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Move AniSkip MAL/title-to-MAL lookup and intro payload resolution from mpv Lua to launcher Electron flow, while keeping mpv-side intro skip UX and chapter/chapter prompt behavior in plugin. Launcher should infer/analyze file metadata, fetch AniSkip payload when launching files, and pass resolved skip window via script options; plugin should trust launcher payload and fall back only when absent.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Launcher infers AniSkip metadata for file targets using existing guessit/fallback logic and performs AniSkip MAL + payload resolution during mpv startup.
|
||||
- [x] #2 Launcher injects script options containing resolved MAL id and intro window fields (or explicit lookup-failure status) into mpv startup.
|
||||
- [x] #3 Lua plugin consumes launcher-provided AniSkip intro data and skips all network lookups when payload is present.
|
||||
- [x] #4 Standalone mpv/plugin usage without launcher payload continues to function using existing async in-plugin lookup path.
|
||||
- [x] #5 Docs and defaults are updated to document new script-option contract.
|
||||
- [x] #6 Launcher tests cover payload generation contract and fallback behavior where metadata is unavailable.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
|
||||
1. Add launcher-side AniSkip payload resolution helpers in launcher/aniskip-metadata.ts (MAL prefix lookup + AniSkip payload fetch + result normalization).
|
||||
2. Wire launcher/mpv.ts + buildSubminerScriptOpts to pass resolved AniSkip fields/mode in --script-opts for file playback.
|
||||
3. Update plugin/subminer/aniskip.lua plus options/state to consume injected payload: if intro_start/end present, apply immediately and skip network lookup; otherwise retain existing async behavior.
|
||||
4. Ensure fallback for standalone mpv usage remains intact for no-launcher/manual refresh.
|
||||
5. Add/update tests/docs/config references for new script-opt contract and edge cases.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Executed end-to-end migration so launcher resolves AniSkip title/MAL/payload before mpv start and injects it via --script-opts. Plugin now parses and consumes launcher payload (JSON/url/base64), applies OP intro from payload, tracks payload metadata in state, and keeps legacy async lookup path for non-launcher/absent payload playback. Added launcher config key aniskip_payload and updated launcher/aniskip-metadata tests for resolve/payload behavior and contract validation.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,50 @@
|
||||
---
|
||||
id: TASK-85
|
||||
title: >-
|
||||
Add launcher dictionary subcommand and initial AniList character dictionary
|
||||
zip generation
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-03 08:47'
|
||||
updated_date: '2026-03-03 08:57'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Implement initial character dictionary flow: launcher `dictionary` subcommand, app `--dictionary` command, AniList media resolution from current playback, Yomitan zip generation to local file, and local cache to avoid repeated API fetches for same AniList id. Manual Yomitan import path only in this phase.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Launcher supports `dictionary` (and alias) and forwards to app command path.
|
||||
- [x] #2 App CLI accepts `--dictionary` and dispatches to dictionary runtime command.
|
||||
- [x] #3 Dictionary command resolves current anime to AniList id, generates Yomitan-compatible zip, and logs output path for manual load.
|
||||
- [x] #4 Generated dictionaries are cached by AniList id so repeated commands reuse existing zip when available.
|
||||
- [x] #5 Backlog task is updated with implementation notes and completion summary for this phase.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Implemented launcher `dictionary`/`dict` subcommand parsing and normalized args flow (`launcher/config/cli-parser-builder.ts`, `launcher/config/args-normalizer.ts`, `launcher/types.ts`).
|
||||
|
||||
Added launcher command dispatch (`launcher/commands/dictionary-command.ts`) and wired `launcher/main.ts` to forward `--dictionary` (plus non-default `--log-level`) to app binary.
|
||||
|
||||
Added app CLI flag `--dictionary` in parser/help and startup routing (`src/cli/args.ts`, `src/cli/help.ts`).
|
||||
|
||||
Added dictionary runtime service (`src/main/character-dictionary-runtime.ts`) that resolves AniList media id from current playback guess, fetches AniList character edges, builds Yomitan-compatible banks/index, writes zip, and caches by AniList id in user data.
|
||||
|
||||
Threaded dictionary generation dependency through CLI runtime/context builders and `src/main.ts` context composition so command executes from launcher/app entrypoints.
|
||||
|
||||
Added/updated tests for parser, command modules, launcher main forwarding, CLI command dispatch, and context/deps wiring. Updated docs for launcher/usage command lists to include dictionary subcommand.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Initial phase shipped: `subminer dictionary` now routes to `SubMiner.AppImage --dictionary`, generates a Yomitan-importable character dictionary zip for the current anime (AniList-based), logs zip output path for manual import, and reuses cached zips by AniList id to avoid repeated API fetches.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: TASK-85
|
||||
title: 'Remove docs Plausible analytics integration'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-03 00:00'
|
||||
updated_date: '2026-03-03 00:00'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 12001
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Remove Plausible analytics integration from docs theme and dependency graph. Keep docs build/runtime analytics-free.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Docs theme no longer imports or initializes Plausible tracker.
|
||||
- [x] #2 `@plausible-analytics/tracker` removed from dependencies and lockfile.
|
||||
- [x] #3 Docs analytics test reflects absence of Plausible wiring.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Deleted Plausible runtime wiring from VitePress theme, removed tracker package via `bun remove`, and updated docs test to assert no Plausible integration remains.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
id: TASK-85.1
|
||||
title: 'Address PR #14 character dictionary review follow-ups'
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-06 07:48'
|
||||
updated_date: '2026-03-06 07:56'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- >-
|
||||
/home/sudacode/projects/japanese/SubMiner/launcher/commands/dictionary-command.ts
|
||||
- >-
|
||||
/home/sudacode/projects/japanese/SubMiner/src/main/character-dictionary-runtime.ts
|
||||
- /home/sudacode/projects/japanese/SubMiner/launcher/types.ts
|
||||
documentation:
|
||||
- 'https://docs.anilist.co/guide/rate-limiting'
|
||||
parent_task_id: TASK-85
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Apply the accepted follow-up fixes from Claude's PR review for the AniList character dictionary work: remove dead launcher code, deduplicate video extension handling where practical, and add explicit pacing for AniList character-page requests / character image downloads so the integration stays within AniList rate-limiting expectations.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Launcher dictionary command no longer contains unreachable dead code after the app handoff.
|
||||
- [x] #2 Character dictionary runtime no longer maintains a separate ad hoc video extension list when existing shared extension data can be reused safely.
|
||||
- [x] #3 Character dictionary generation spaces outbound AniList-related requests with explicit named delays, and tests cover the pacing behavior and unchanged command forwarding behavior.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Add failing tests for dictionary command handoff semantics and dictionary runtime request pacing.
|
||||
2. Remove unreachable boolean return path from the launcher dictionary command while preserving call sites.
|
||||
3. Reuse the shared launcher video extension set inside the character dictionary runtime with extname normalization, then add named AniList pacing constants for page fetches and character image downloads.
|
||||
4. Run targeted tests, then broader relevant test slices, and update acceptance criteria / notes with the validated result.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Added a shared `src/shared/video-extensions.ts` source and rewired both launcher/runtime consumers to remove the duplicated runtime extension list.
|
||||
|
||||
Replaced the hardcoded AniList page sleep with a per-generation AniList request pacer (2000ms between API requests) plus 250ms spacing between character image download attempts, including failed image fetches.
|
||||
|
||||
Hardened `runDictionaryCommand` so an unexpected return from the `never`-typed app handoff throws immediately instead of silently falling through.
|
||||
|
||||
Validated with targeted and adjacent test slices plus `bun run tsc --noEmit`.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Removed the dead post-handoff return from the launcher dictionary command and replaced it with an explicit invariant error if the `never`-typed app handoff ever returns unexpectedly. Extracted video extension data into `src/shared/video-extensions.ts` so the launcher and character dictionary runtime share one source of truth.
|
||||
|
||||
Adjusted character dictionary generation to use a per-run AniList request pacer with a conservative 2000ms delay between AniList API calls, and added 250ms spacing between character image download attempts so repeated image fetches are not bursty even when an image URL fails. Added regression coverage for the pacing behavior and the launcher handoff invariant.
|
||||
|
||||
Validation: `bun test src/main/character-dictionary-runtime.test.ts`, `bun test launcher/commands/command-modules.test.ts`, `bun test launcher/main.test.ts launcher/parse-args.test.ts src/cli/args.test.ts src/core/services/cli-command.test.ts src/main/runtime/character-dictionary-auto-sync.test.ts`, `bun run tsc --noEmit`.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
id: TASK-86
|
||||
title: 'Renderer: keyboard-driven Yomitan lookup mode and popup key forwarding'
|
||||
status: Done
|
||||
assignee:
|
||||
- Codex
|
||||
created_date: '2026-03-04 13:40'
|
||||
updated_date: '2026-03-05 11:30'
|
||||
labels:
|
||||
- enhancement
|
||||
- renderer
|
||||
- yomitan
|
||||
dependencies:
|
||||
- TASK-77
|
||||
references:
|
||||
- src/renderer/handlers/keyboard.ts
|
||||
- src/renderer/handlers/mouse.ts
|
||||
- src/renderer/renderer.ts
|
||||
- src/renderer/state.ts
|
||||
- src/renderer/yomitan-popup.ts
|
||||
- src/core/services/overlay-window.ts
|
||||
- src/preload.ts
|
||||
- src/shared/ipc/contracts.ts
|
||||
- src/types.ts
|
||||
- vendor/yomitan/js/app/frontend.js
|
||||
- vendor/yomitan/js/app/popup.js
|
||||
- vendor/yomitan/js/display/display.js
|
||||
- vendor/yomitan/js/display/popup-main.js
|
||||
- vendor/yomitan/js/display/display-audio.js
|
||||
documentation:
|
||||
- README.md
|
||||
- docs/usage.md
|
||||
- docs/shortcuts.md
|
||||
priority: medium
|
||||
ordinal: 13000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Add true keyboard-driven token lookup flow in overlay:
|
||||
|
||||
- Toggle keyboard token-selection mode and navigate tokens by keyboard (`Arrow` + `HJKL`).
|
||||
- Toggle Yomitan lookup window for selected token via fixed accelerator (`Ctrl/Cmd+Y`) without requiring mouse click.
|
||||
- Preserve keyboard-only workflow while popup is open by forwarding popup keys (`J/K`, `M`, `P`, `[`, `]`) and restoring overlay focus on popup close.
|
||||
- Ensure selection styling and hover metadata tooltips (frequency/JLPT) work for keyboard-selected token.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Keyboard mode toggle exists and shows visual selection outline for active token.
|
||||
- [x] #2 Navigation works via arrows and vim keys while keyboard mode is enabled.
|
||||
- [x] #3 Lookup window toggles from selected token with `Ctrl/Cmd+Y`; close path restores overlay keyboard focus.
|
||||
- [x] #4 Popup-local controls work via keyboard forwarding (`J/K`, `M`, `P`, `[`, `]`), including mine action.
|
||||
- [x] #5 Frequency/JLPT hover tags render for keyboard-selected token.
|
||||
- [x] #6 Renderer/runtime tests cover new visibility/selection behavior, and docs are updated.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Implemented keyboard-driven Yomitan workflow end-to-end in renderer + bundled Yomitan runtime bridge. Added overlay-level keyboard mode state, token selection sync, lookup toggle routing, popup command forwarding, and focus recovery after popup close. Follow-up fixes kept lookup open while moving between tokens, made popup-local `J/K` and `ArrowUp/ArrowDown` scroll work from overlay-owned focus with key repeat, skipped keyboard/token annotation flow for parser groups that have no dictionary-backed headword, and preserved paused playback when token navigation jumps across subtitle lines. Updated user docs/README to document the final shortcut behavior.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
id: TASK-86
|
||||
title: >-
|
||||
Require target path for launcher dictionary command and forward dictionary
|
||||
target to app runtime
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-03 09:22'
|
||||
updated_date: '2026-03-03 09:53'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Change dictionary flow so launcher uses `subminer dictionary <file-or-directory>` and forwards target to app without playback launch. Keep direct app `--dictionary` behavior for in-session/mpv-triggered use, adding optional `--dictionary-target` path override.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Launcher `dictionary`/`dict` requires a target path argument and parses optional log level.
|
||||
- [x] #2 Launcher forwards target to app as `--dictionary-target <path>` together with `--dictionary`.
|
||||
- [x] #3 App CLI parses optional `--dictionary-target` and dictionary command passes it into dictionary runtime.
|
||||
- [x] #4 Dictionary runtime resolves explicit file/directory target for AniList guess flow; falls back to current playback when target is absent.
|
||||
- [x] #5 Docs/tests are updated for new launcher invocation syntax.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Launcher dictionary subcommand now requires a positional target path (`subminer dictionary <path>` / `dict <path>`) via commander argument wiring in `launcher/config/cli-parser-builder.ts`.
|
||||
|
||||
Added `dictionaryTarget` flow in launcher normalization/types and path validation (must be existing local file or directory) in `launcher/config/args-normalizer.ts`.
|
||||
|
||||
Launcher dictionary command now forwards `--dictionary --dictionary-target <resolved-path>` to app binary in `launcher/commands/dictionary-command.ts`.
|
||||
|
||||
App CLI now parses optional `--dictionary-target` in `src/cli/args.ts`; dictionary handler passes target through to runtime in `src/core/services/cli-command.ts`.
|
||||
|
||||
Dictionary runtime now resolves explicit target inputs in `src/main/character-dictionary-runtime.ts`: file target uses file path/title; directory target selects first video file recursively (fallback to directory name), and still falls back to current playback when target is absent.
|
||||
|
||||
Updated context/dependency pass-through for dictionary target argument (`src/main.ts`, `src/main/runtime/cli-command-context-main-deps.ts`).
|
||||
|
||||
Updated tests/docs for new syntax and forwarding behavior (`launcher/main.test.ts`, `launcher/parse-args.test.ts`, `launcher/commands/command-modules.test.ts`, `src/cli/args.test.ts`, `src/cli/help.test.ts`, `docs/usage.md`, `docs/launcher-script.md`).
|
||||
|
||||
Follow-up noise reduction: dictionary commands now opt into lightweight startup path by extending `shouldSkipHeavyStartup` in `src/main.ts` to include `initialArgs.dictionary`. This skips heavy app-ready initialization (mpv client creation/background warmups/overlay bootstrap) for dictionary CLI runs.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Launcher dictionary flow now uses explicit targets: run `subminer dictionary <file-or-directory>`. It forwards target to app and performs dictionary generation without depending on currently playing media. Direct app `--dictionary` remains available for in-session/mpv-triggered workflows, with optional `--dictionary-target` override support.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,79 @@
|
||||
---
|
||||
id: TASK-87
|
||||
title: >-
|
||||
Codebase health: harden verification and retire dead architecture identified
|
||||
in the March 2026 review
|
||||
status: In Progress
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:19'
|
||||
updated_date: '2026-03-06 11:11'
|
||||
labels:
|
||||
- tech-debt
|
||||
- tests
|
||||
- maintainability
|
||||
milestone: m-0
|
||||
dependencies: []
|
||||
references:
|
||||
- package.json
|
||||
- README.md
|
||||
- src/main.ts
|
||||
- src/anki-integration.ts
|
||||
- src/core/services/immersion-tracker-service.test.ts
|
||||
- src/translators/index.ts
|
||||
- src/subsync/engines.ts
|
||||
- src/subtitle/pipeline.ts
|
||||
- backlog/tasks/task-87.5 - Dead-architecture-cleanup-delete-unused-registry-and-pipeline-modules-that-are-off-the-live-path.md
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Track the remediation work from the March 6, 2026 code review. The review found that the default test gate only exercises 53 of 241 test files, the dedicated subtitle test lane is a no-op, SQLite-backed immersion tracking tests are conditionally skipped in the standard Bun run, src/main.ts still contains a large dead-symbol backlog, several registry/pipeline modules appear unreferenced from live execution paths, and src/anki-integration.ts remains an oversized orchestration file. This parent task should coordinate a safe sequence: improve verification first, then remove dead code and continue decomposition with good test coverage in place.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [ ] #1 Child tasks are created for each remediation workstream with explicit dependencies and enough context for an isolated agent to execute them.
|
||||
- [ ] #2 The parent task records the recommended sequencing and parallelization strategy so replacement agents can resume without conversation history.
|
||||
- [ ] #3 Completion of the parent task leaves the repository with a materially more trustworthy test gate, less dead architecture, and clearer ownership boundaries for the main runtime and Anki integration surfaces.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
|
||||
Recommended sequencing:
|
||||
|
||||
1. Run TASK-87.1, TASK-87.2, TASK-87.3, and TASK-87.7 first. These are the safety-net and tooling tasks and can largely proceed in parallel.
|
||||
2. Start TASK-87.4 once TASK-87.1 lands so src/main.ts cleanup happens under a more trustworthy test matrix.
|
||||
3. Start TASK-87.5 after TASK-87.1 and TASK-87.2 so dead subsync/pipeline cleanup happens with stronger subtitle and runtime verification.
|
||||
4. Start TASK-87.6 after TASK-87.1 so Anki refactors happen with broader default coverage in place.
|
||||
5. Keep PRs focused: do not combine verification work with architectural cleanup unless a narrow dependency requires it.
|
||||
|
||||
Parallelization guidance:
|
||||
|
||||
- Wave 1 parallel: TASK-87.1, TASK-87.2, TASK-87.3, TASK-87.7
|
||||
- Wave 2 parallel: TASK-87.4, TASK-87.5, TASK-87.6
|
||||
|
||||
Shared review context to restate in child tasks:
|
||||
|
||||
- Standard test scripts currently reference only 53 unique test files out of 241 discovered test and type-test files under src/ and launcher/.
|
||||
- test:subtitle is currently a placeholder echo even though subtitle sync is a user-facing feature.
|
||||
- SQLite-backed immersion tracker tests are conditionally skipped in the standard Bun run.
|
||||
- src/main.ts trips many noUnusedLocals/noUnusedParameters diagnostics.
|
||||
- src/translators/index.ts, src/subsync/engines.ts, src/subtitle/pipeline.ts, src/tokenizers/index.ts, and src/token-mergers/index.ts appeared unreferenced during review and must be re-verified before deletion.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Progress Notes
|
||||
|
||||
- `TASK-87.5` is complete. The isolated dead registry/pipeline modules were re-verified as off the maintained runtime path and removed.
|
||||
- Live subtitle tokenization now owns the zero-width separator normalization that previously only existed in the dead subtitle pipeline path, so the cleanup did not drop that behavior.
|
||||
- Verification completed for the cleanup slice with `bun test src/core/services/tokenizer.test.ts`, `bun test src/dead-architecture-cleanup.test.ts`, `bun test src/core/services/subsync.test.ts src/subsync/utils.test.ts`, `bun run tsc`, and `bun run test:src`.
|
||||
- Remaining parent-task scope still includes the broader verification hardening, `src/main.ts` dead-symbol cleanup, and `src/anki-integration.ts` decomposition work tracked by the other child tasks.
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
id: TASK-87.1
|
||||
title: >-
|
||||
Testing workflow: make standard test commands reflect the maintained test
|
||||
surface
|
||||
status: Done
|
||||
assignee:
|
||||
- OpenCode
|
||||
created_date: '2026-03-06 03:19'
|
||||
updated_date: '2026-03-06 08:52'
|
||||
labels:
|
||||
- tests
|
||||
- maintainability
|
||||
milestone: m-0
|
||||
dependencies: []
|
||||
references:
|
||||
- package.json
|
||||
- src/main-entry-runtime.test.ts
|
||||
- src/anki-integration/anki-connect-proxy.test.ts
|
||||
- src/main/runtime/jellyfin-remote-playback.test.ts
|
||||
- src/main/runtime/registry.test.ts
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
parent_task_id: TASK-87
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
The current package scripts hand-enumerate a small subset of test files, which leaves the standard green signal misleading. A local audit found 241 test/type-test files under src/ and launcher/, but only 53 unique files referenced by the standard package.json test scripts. This task should redesign the runnable test matrix so maintained tests are either executed by the standard commands or intentionally excluded through a documented rule, instead of silently drifting out of coverage.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 The repository has a documented and reproducible test matrix for standard development commands, including which suites belong in the default lane versus slower or environment-specific lanes.
|
||||
- [x] #2 The standard test entrypoints stop relying on a brittle hand-maintained allowlist for the currently covered unit and integration suites, or an explicit documented mechanism exists that prevents silent omission of new tests.
|
||||
- [x] #3 Representative tests that were previously outside the standard lane from src/main/runtime, src/anki-integration, and entry/runtime surfaces are executed by an automated command and included in the documented matrix.
|
||||
- [x] #4 Documentation for contributors explains which command to run for fast verification, full verification, and environment-specific verification.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Update `package.json` to replace the current file-by-file test allowlists with a documented lane matrix: keep `test`/`test:fast` as the quick default lane, add `test:full` for the maintained source test surface, and add `test:env` for slower or environment-specific checks.
|
||||
2. Use directory-based discovery for maintained suites so new tests under stable surfaces such as `src/main`, `src/anki-integration`, and `launcher` are not silently omitted by default script maintenance.
|
||||
3. Split environment-specific verification into explicit commands for checks such as launcher smoke/plugin coverage and sqlite-gated tests, instead of leaving them undocumented or mixed into the default signal.
|
||||
4. Update `README.md` with a contributor-facing testing matrix that explains fast, full, and environment-specific verification, including any prerequisites or expected skip behavior.
|
||||
5. Verify the matrix by running representative targeted tests plus the documented lane commands, demonstrating that previously omitted entry/runtime, Anki integration, and main runtime tests now belong to automated commands.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Reviewed task context via Backlog MCP plus repo audit. Current package.json test scripts still rely on hand-maintained file allowlists and omit large maintained areas including src/main/runtime, src/anki-integration, and src/main-entry-runtime.test.ts. Preparing an implementation plan and contributor-facing test matrix update before code changes.
|
||||
|
||||
Saved detailed implementation plan to docs/plans/2026-03-06-testing-workflow-test-matrix.md and recorded the approved direction in the Backlog task before implementation.
|
||||
|
||||
Implemented a lane-based test matrix. Added `scripts/run-test-lane.mjs` so Bun-managed `src/**` and launcher unit lanes discover files automatically while excluding a small explicit Node-only set instead of relying on large hand-maintained allowlists. Added `test:node:compat` for `ipc`, `anki-jimaku-ipc`, `overlay-manager`, `config-validation`, `startup-config`, and `registry` suites, kept `test:env` for launcher smoke/plugin plus SQLite-backed immersion checks, and updated `README.md` with the contributor-facing matrix and exclusions.
|
||||
|
||||
Validated the new matrix with `bun run test:fast`, `bun run test:full`, `bun run test:env`, `bun run test:src`, `bun run test:launcher:unit:src`, `bun run test:node:compat`, and targeted `bun test src/core/services/anilist/anilist-updater.test.ts`. Representative previously omitted surfaces now run through automated commands: `src/main-entry-runtime.test.ts` via `test:fast`, `src/anki-integration/anki-connect-proxy.test.ts` via `test:fast`/`test:src`, and `src/main/runtime/registry.test.ts` via `test:node:compat`/`test:full`.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Reworked the repository test matrix so standard commands reflect the maintained test surface without relying on brittle file allowlists. Added automated Bun discovery lanes for Bun-compatible `src/**` and launcher unit suites, a documented Node compatibility lane for Electron/sqlite-sensitive tests, and updated the contributor docs with fast/full/environment-specific guidance plus explicit exclusions. Verified with `bun run test:fast`, `bun run test:full`, and `bun run test:env`, along with the component lanes and targeted regression coverage for the updated AniList guessit test seam.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,84 @@
|
||||
---
|
||||
id: TASK-87.2
|
||||
title: >-
|
||||
Subtitle sync verification: replace the no-op subtitle lane with real
|
||||
automated coverage
|
||||
status: Done
|
||||
assignee:
|
||||
- Kyle Yasuda
|
||||
created_date: '2026-03-06 03:19'
|
||||
updated_date: '2026-03-06 08:06'
|
||||
labels:
|
||||
- tests
|
||||
- subsync
|
||||
milestone: m-0
|
||||
dependencies: []
|
||||
references:
|
||||
- package.json
|
||||
- README.md
|
||||
- src/core/services/subsync.ts
|
||||
- src/core/services/subsync.test.ts
|
||||
- src/subsync/utils.ts
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
parent_task_id: TASK-87
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
SubMiner advertises subtitle syncing with alass and ffsubsync, but the dedicated test:subtitle command currently does not run any tests. There is already lower-level coverage in src/core/services/subsync.test.ts, but the test matrix and contributor-facing commands do not reflect that reality. This task should replace the no-op lane with real verification, align scripts with the existing subsync test surface, and make the user-facing docs honest about how subtitle sync is verified.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 The test:subtitle entrypoint runs real automated verification instead of echoing a placeholder message.
|
||||
- [x] #2 The subtitle verification lane covers both alass and ffsubsync behavior, including at least one non-happy-path scenario relevant to current functionality.
|
||||
- [x] #3 Contributor-facing documentation points to the real subtitle verification command and no longer implies a dedicated test lane exists when it does not.
|
||||
- [x] #4 The resulting verification strategy integrates cleanly with the repository-wide test matrix without duplicating or hiding existing subsync coverage.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
Plan of record:
|
||||
|
||||
1. Replace the placeholder package-script lane with a real `test:subtitle:src` command that runs the maintained subtitle-sync tests directly (`src/core/services/subsync.test.ts` and `src/subsync/utils.test.ts`), and point `test:subtitle` at that lane instead of build+echo behavior.
|
||||
2. Add one focused ffsubsync non-happy-path test in `src/core/services/subsync.test.ts` so the dedicated lane explicitly covers both engines plus failure propagation relevant to current functionality.
|
||||
3. Update `README.md` contributor guidance to name `bun run test:subtitle` as the subtitle verification command and explain that it reuses the maintained subsync tests already included in broader core coverage.
|
||||
4. Verify the final strategy by running `bun run test:subtitle` and `bun run test:core:src` so the dedicated lane stays aligned with the repository-wide matrix instead of creating a divergent hidden suite.
|
||||
|
||||
Detailed execution plan saved at `docs/plans/2026-03-06-subtitle-sync-verification.md`.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Reviewed task references and current subtitle verification surface. Existing coverage already lives in `src/core/services/subsync.test.ts` and `src/subsync/utils.test.ts`; `test:subtitle` is still a placeholder build+echo wrapper. The referenced report `docs/reports/2026-02-22-task-100-dead-code-report.md` is not present in the workspace, so planning used the task body plus repository state instead.
|
||||
|
||||
Implementation plan written and saved to `docs/plans/2026-03-06-subtitle-sync-verification.md`. Proceeding with execution per the task request.
|
||||
|
||||
Replaced the placeholder subtitle lane with `test:subtitle:src` in `package.json`, pointing `test:subtitle` directly at `src/core/services/subsync.test.ts` and `src/subsync/utils.test.ts` instead of build+echo behavior.
|
||||
|
||||
Added explicit ffsubsync failure-path coverage in `src/core/services/subsync.test.ts`, asserting non-zero command failures surface detailed `ffsubsync synchronization failed` messaging alongside existing alass coverage.
|
||||
|
||||
Updated `README.md` verification guidance to point contributors at `bun run test:subtitle` and explain that the lane reuses the maintained subsync tests already included in `bun run test:core`.
|
||||
|
||||
Verification: `bun run test:subtitle` passed (15 tests across 2 files). `bun run test:core:src` also passed (373 pass, 6 skip, 0 fail), confirming the dedicated subtitle lane stays aligned with the broader matrix.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Implemented a real subtitle verification lane by replacing the placeholder `test:subtitle` build+echo flow with a source-level `test:subtitle:src` command that runs the maintained subtitle-sync tests directly from `src/core/services/subsync.test.ts` and `src/subsync/utils.test.ts`. This keeps subtitle verification explicit for contributors while still reusing the same maintained test surface already covered by `test:core`.
|
||||
|
||||
Expanded subtitle-sync coverage with an explicit ffsubsync failure-path test so the dedicated lane now exercises both engines plus a user-visible non-happy path. Updated `README.md` to document `bun run test:subtitle` as the contributor-facing subtitle verification command and to explain its relationship to the broader core suite.
|
||||
|
||||
Verification run:
|
||||
- `bun run test:subtitle`
|
||||
- `bun run test:core:src`
|
||||
|
||||
Notes:
|
||||
- The task reference `docs/reports/2026-02-22-task-100-dead-code-report.md` was not present in the workspace during execution, so implementation used the task body and live repository state as the source of truth.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,75 @@
|
||||
---
|
||||
id: TASK-87.3
|
||||
title: >-
|
||||
Immersion tracking verification: make SQLite-backed persistence tests visible
|
||||
and reproducible
|
||||
status: Done
|
||||
assignee:
|
||||
- Kyle Yasuda
|
||||
created_date: '2026-03-06 03:19'
|
||||
updated_date: '2026-03-06 08:20'
|
||||
labels:
|
||||
- tests
|
||||
- immersion-tracking
|
||||
milestone: m-0
|
||||
dependencies: []
|
||||
references:
|
||||
- src/core/services/immersion-tracker-service.test.ts
|
||||
- src/core/services/immersion-tracker/storage-session.test.ts
|
||||
- src/core/services/immersion-tracker-service.ts
|
||||
- package.json
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
parent_task_id: TASK-87
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
The immersion tracker is persistence-heavy, but its SQLite-backed tests are conditionally skipped in the standard Bun run when node:sqlite support is unavailable. That creates a blind spot around session finalization, telemetry persistence, and retention behavior. This task should establish a reliable automated verification path for the database-backed cases and make the prerequisite/runtime behavior explicit to contributors and CI.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Database-backed immersion tracking tests run in at least one documented automated command that is practical for contributors or CI to execute.
|
||||
- [x] #2 If the current runtime cannot execute the SQLite-backed tests, the repository exposes that limitation clearly instead of silently reporting a misleading green result.
|
||||
- [x] #3 Contributor-facing documentation explains how to run the immersion tracker verification lane and any environment prerequisites it depends on.
|
||||
- [x] #4 The resulting verification covers session persistence or finalization behavior that is not exercised by the pure seam tests alone.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
Implementation plan recorded in `docs/plans/2026-03-06-immersion-sqlite-verification.md`.
|
||||
|
||||
1. Update `src/core/services/immersion-tracker-service.test.ts` and `src/core/services/immersion-tracker/storage-session.test.ts` so unsupported `node:sqlite` runtimes emit an explicit skip reason instead of a silent top-level skip alias.
|
||||
2. Add a dedicated `package.json` SQLite verification lane that runs both immersion persistence suites together under a runtime with `node:sqlite` support, likely via built `dist/**` tests executed by Node.
|
||||
3. Wire that lane into `.github/workflows/ci.yml` and `.github/workflows/release.yml` so automated verification includes a real DB-backed persistence/finalization check.
|
||||
4. Document the new command, prerequisites, and coverage in `README.md`, including the distinction between Bun's default lane and the reproducible SQLite lane.
|
||||
5. Validate the final lane by running the dedicated command and confirming it exercises persistence/finalization behavior beyond the seam-only tests.
|
||||
|
||||
Execution adjustment: the reproducible lane uses `node --experimental-sqlite --test ...` because Node 22 exposes `node:sqlite` behind the experimental flag. Running that lane also exposed placeholder-count mismatches in `src/core/services/immersion-tracker/storage.ts`, so the final implementation includes a small SQL placeholder fix required for the new cross-runtime verification path.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Confirmed Bun 1.3.5 lacks `node:test` `t.skip()` support, so explicit unsupported-runtime messaging is surfaced with file-level warnings while the SQLite-backed tests remain conditionally skipped.
|
||||
|
||||
Added `test:immersion:sqlite:src`, `test:immersion:sqlite:dist`, and `test:immersion:sqlite` scripts; the source lane now prints explicit warnings when `node:sqlite` is unavailable, and the dist lane runs both SQLite-backed immersion suites under Node with `--experimental-sqlite`.
|
||||
|
||||
Wired the dist SQLite lane into `.github/workflows/ci.yml` and `.github/workflows/release.yml` after the bundle build, with explicit `actions/setup-node@v4` provisioning for Node 22.12.0.
|
||||
|
||||
Fixed SQL prepared-statement placeholder counts in `src/core/services/immersion-tracker/storage.ts`, which the new Node-backed SQLite lane surfaced immediately.
|
||||
|
||||
Verification: `bun run test:immersion:sqlite:src` -> pass with explicit unsupported-runtime warnings and 10 skips under Bun 1.3.5; `bun run test:immersion:sqlite` -> pass with 14/14 tests under Node 22.12.0 + `--experimental-sqlite`.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Added an explicit SQLite-backed immersion verification lane and documented it so persistence-heavy coverage is no longer hidden behind Bun-only skips. `package.json` now exposes source and dist SQLite scripts, the source test files print actionable warnings when `node:sqlite` is unavailable, and `README.md` explains the dedicated contributor command plus its Node 22 `--experimental-sqlite` prerequisite.
|
||||
|
||||
Automated verification now includes the new dist lane in both `.github/workflows/ci.yml` and `.github/workflows/release.yml` after build output is available. While wiring the reproducible Node lane, it exposed placeholder-count mismatches in `src/core/services/immersion-tracker/storage.ts`; fixing those placeholders makes the SQLite-backed persistence/finalization tests pass cross-runtime, covering session finalization, telemetry persistence, and storage-session write paths.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,63 @@
|
||||
---
|
||||
id: TASK-87.4
|
||||
title: >-
|
||||
Runtime composition root: remove dead symbols and tighten module boundaries in
|
||||
src/main.ts
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:19'
|
||||
updated_date: '2026-03-06 18:10'
|
||||
labels:
|
||||
- tech-debt
|
||||
- runtime
|
||||
- maintainability
|
||||
milestone: m-0
|
||||
dependencies:
|
||||
- TASK-87.1
|
||||
references:
|
||||
- src/main.ts
|
||||
- src/main/runtime
|
||||
- package.json
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
parent_task_id: TASK-87
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
A noUnusedLocals/noUnusedParameters compile pass reports a large concentration of dead imports and dead locals in src/main.ts. The file is also far beyond the repo’s preferred size guideline, which makes the runtime composition root difficult to review and easy to break. This task should remove confirmed dead symbols, continue extracting coherent slices where that improves readability, and leave the entrypoint materially easier to understand without changing behavior.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 src/main.ts no longer emits dead-symbol diagnostics under a noUnusedLocals/noUnusedParameters compile pass for the areas touched by this cleanup.
|
||||
- [x] #2 Unused imports, destructured values, and stale locals identified in the current composition root are removed or relocated without behavior changes.
|
||||
- [x] #3 The resulting composition root has clearer ownership boundaries for at least one runtime slice that is currently buried in the monolith.
|
||||
- [x] #4 Relevant runtime and startup verification commands pass after the cleanup, and any command changes are documented if needed.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
|
||||
1. Re-run the noUnusedLocals/noUnusedParameters compile pass and capture the src/main.ts diagnostics cluster before editing.
|
||||
2. Remove dead imports, destructured values, and stale locals in small reviewable slices; extract a coherent helper/module only where that materially reduces coupling.
|
||||
3. Keep changes behavior-preserving and avoid mixing unrelated cleanup outside src/main.ts unless required to compile.
|
||||
4. Verify with the updated runtime/startup test commands from TASK-87.1 plus a noUnused compile pass.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Completion Notes
|
||||
|
||||
- Removed the dead import/destructure backlog from `src/main.ts` and deleted stale wrapper seams that no longer owned runtime behavior after the composer/runtime extractions.
|
||||
- Tightened module boundaries so the composition root depends on the composed/public runtime surfaces it actually uses instead of retaining unused lower-level domain factory symbols.
|
||||
- Cleared the remaining strict `noUnusedLocals`/`noUnusedParameters` failures in nearby touched files required for a clean repo-wide pass: `launcher/commands/playback-command.ts`, `src/anki-integration.ts`, `src/anki-integration/field-grouping-workflow.ts`, `src/core/services/tokenizer/yomitan-parser-runtime.test.ts`, and `src/main/runtime/composers/composer-contracts.type-test.ts`.
|
||||
- Verification:
|
||||
- `bunx tsc --noEmit -p tsconfig.typecheck.json --noUnusedLocals --noUnusedParameters --pretty false`
|
||||
- `bun run test:fast`
|
||||
- Commit: `e659b5d` (`refactor(runtime): remove dead symbols from composition roots`)
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
id: TASK-87.5
|
||||
title: >-
|
||||
Dead architecture cleanup: delete unused registry and pipeline modules that
|
||||
are off the live path
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:20'
|
||||
updated_date: '2026-03-06 11:05'
|
||||
labels:
|
||||
- tech-debt
|
||||
- dead-code
|
||||
milestone: m-0
|
||||
dependencies:
|
||||
- TASK-87.1
|
||||
- TASK-87.2
|
||||
references:
|
||||
- src/translators/index.ts
|
||||
- src/subsync/engines.ts
|
||||
- src/subtitle/pipeline.ts
|
||||
- src/tokenizers/index.ts
|
||||
- src/token-mergers/index.ts
|
||||
- src/core/services/subsync.ts
|
||||
- src/core/services/tokenizer.ts
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
parent_task_id: TASK-87
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
The review found several modules that appear self-contained but unused from the application’s live execution paths: src/translators/index.ts, src/subsync/engines.ts, src/subtitle/pipeline.ts, src/tokenizers/index.ts, and src/token-mergers/index.ts. At the same time, the real runtime behavior is implemented elsewhere. This task should verify those modules are truly unused, remove or consolidate them, and clean up any stale exports, docs, or tests so contributors are not misled by duplicate architecture.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Each candidate module identified in the review is either removed as dead code or justified and reconnected to a real supported execution path.
|
||||
- [x] #2 Any stale exports, imports, or tests associated with the removed or consolidated modules are cleaned up so the codebase has a single obvious path for the affected behavior.
|
||||
- [x] #3 The cleanup does not regress live tokenization or subtitle sync behavior and the relevant verification commands remain green.
|
||||
- [x] #4 Contributor-facing documentation or internal notes no longer imply that removed duplicate architecture is part of the current design.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
|
||||
1. Re-verify each candidate module is off the live path by tracing imports from current runtime entrypoints before deleting anything.
|
||||
2. Remove or consolidate truly dead modules and clean associated exports/imports/tests so only the supported path remains obvious.
|
||||
3. Pay special attention to subtitle sync and tokenization surfaces, since duplicate architecture exists near active code.
|
||||
4. Verify the relevant tokenization and subsync commands/tests still pass and update any stale docs or notes.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- Traced imports from `src/main.ts`, `src/main/runtime/**`, `src/core/services/subsync-runner.ts`, and `src/core/services/tokenizer.ts`; confirmed the candidate registry/pipeline modules were isolated from the maintained runtime path.
|
||||
- Deleted dead modules: `src/translators/index.ts`, `src/subsync/engines.ts`, `src/subtitle/pipeline.ts`, `src/subtitle/stages/{merge,normalize,tokenize}.ts`, `src/subtitle/stages/normalize.test.ts`, `src/tokenizers/index.ts`, and `src/token-mergers/index.ts`.
|
||||
- Moved the useful zero-width separator normalization into the live tokenizer path in `src/core/services/tokenizer.ts` and added regression coverage plus a repository-level dead-architecture guard in `src/dead-architecture-cleanup.test.ts`.
|
||||
- Verified with `bun test src/core/services/tokenizer.test.ts`, `bun test src/dead-architecture-cleanup.test.ts`, `bun test src/core/services/subsync.test.ts src/subsync/utils.test.ts`, `bun run tsc`, and `bun run test:src`.
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
id: TASK-87.6
|
||||
title: >-
|
||||
Anki integration maintainability: continue decomposing the oversized
|
||||
orchestration layer
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:20'
|
||||
updated_date: '2026-03-06 09:23'
|
||||
labels:
|
||||
- tech-debt
|
||||
- anki
|
||||
- maintainability
|
||||
milestone: m-0
|
||||
dependencies:
|
||||
- TASK-87.1
|
||||
references:
|
||||
- src/anki-integration.ts
|
||||
- src/anki-integration/field-grouping-workflow.ts
|
||||
- src/anki-integration/note-update-workflow.ts
|
||||
- src/anki-integration/card-creation.ts
|
||||
- src/anki-integration/anki-connect-proxy.ts
|
||||
- src/anki-integration.test.ts
|
||||
documentation:
|
||||
- docs/reports/2026-02-22-task-100-dead-code-report.md
|
||||
- docs/anki-integration.md
|
||||
parent_task_id: TASK-87
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
src/anki-integration.ts remains an oversized orchestration file even after earlier extractions. It still mixes config normalization, polling setup, media generation, duplicate resolution, field grouping workflows, and user feedback coordination in one class. This task should continue the decomposition so the remaining orchestration surface is smaller and easier to reason about, while preserving existing Anki, proxy, field grouping, and note update behavior.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 The responsibilities currently concentrated in src/anki-integration.ts are split into clearer modules or services with narrow ownership boundaries.
|
||||
- [x] #2 The resulting orchestration surface is materially smaller and easier to review, with at least one mixed-responsibility cluster extracted behind a well-named interface.
|
||||
- [x] #3 Existing Anki integration behavior remains covered by automated verification, including note update, field grouping, and proxy-related flows that the refactor touches.
|
||||
- [x] #4 Any developer-facing docs or notes needed to understand the new structure are updated in the same task.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
|
||||
1. Map the remaining responsibility clusters inside src/anki-integration.ts and choose one or more extraction seams that reduce mixed concerns without changing behavior.
|
||||
2. Move logic behind narrow interfaces/modules rather than creating another giant helper; keep orchestration readable.
|
||||
3. Preserve coverage for field grouping, note update, proxy, and card creation flows touched by the refactor.
|
||||
4. Update docs or internal notes if the new structure changes where contributors should look for a given behavior.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
@@ -0,0 +1,68 @@
|
||||
---
|
||||
id: TASK-88
|
||||
title: >-
|
||||
Fix second-instance --start handling when overlay runtime is already
|
||||
initialized
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-06 07:30'
|
||||
updated_date: '2026-03-06 07:31'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/core/services/cli-command.ts
|
||||
- >-
|
||||
/home/sudacode/projects/japanese/SubMiner/src/core/services/cli-command.test.ts
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Restore the CLI command guard so a second-instance `--start` request does not reconnect or reinitialize overlay work when the overlay runtime is already active, while preserving other second-instance commands.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Second-instance `--start` logs that the app is already running when the overlay runtime is initialized.
|
||||
- [x] #2 Second-instance `--start` does not reconnect the MPV client when the overlay runtime is already initialized.
|
||||
- [x] #3 Second-instance commands that include non-start actions still execute those actions.
|
||||
- [x] #4 Regression coverage documents the guarded second-instance behavior.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Reproduce the failing `handleCliCommand` second-instance `--start` regression in `src/core/services/cli-command.test.ts`.
|
||||
2. Update `src/core/services/cli-command.ts` so second-instance `--start` is ignored when the overlay runtime is already initialized, while still allowing non-start actions in the same invocation.
|
||||
3. Run focused CLI command tests, then rerun the core test target if practical, and record acceptance criteria/results.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Reproduced the failing second-instance `--start` regression in `src/core/services/cli-command.test.ts` before editing.
|
||||
|
||||
Restored a guard in `src/core/services/cli-command.ts` that ignores second-instance `--start` when the overlay runtime is already initialized, but still allows other flags in the same invocation to run.
|
||||
|
||||
Verification: `bun test src/core/services/cli-command.test.ts`, `bun run test:core:src`, and `bun run test` all pass; the six immersion tracker tests remain skipped as before.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Restored the missing second-instance `--start` guard in `src/core/services/cli-command.ts`.
|
||||
|
||||
- Added an `ignoreSecondInstanceStart` check so `handleCliCommand` logs `Ignoring --start because SubMiner is already running.` when a second-instance `--start` arrives after the overlay runtime is already initialized.
|
||||
- Updated start gating so pure duplicate `--start` requests no longer reconnect the MPV client, while combined commands such as `--start --toggle-visible-overlay` still execute their non-start behavior and can connect through those paths.
|
||||
- Verified the regression with existing CLI command coverage and reran the broader test targets.
|
||||
|
||||
Tests run:
|
||||
- `bun test src/core/services/cli-command.test.ts`
|
||||
- `bun run test:core:src`
|
||||
- `bun run test`
|
||||
|
||||
Notes:
|
||||
- The six skipped immersion tracker tests are unchanged from the pre-fix baseline.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,57 @@
|
||||
---
|
||||
id: TASK-89
|
||||
title: Replace per-anime Yomitan imports with merged usage-based character dictionary
|
||||
status: Done
|
||||
assignee:
|
||||
- '@codex'
|
||||
created_date: '2026-03-06 07:59'
|
||||
updated_date: '2026-03-06 08:09'
|
||||
labels:
|
||||
- character-dictionary
|
||||
- yomitan
|
||||
- anilist
|
||||
dependencies: []
|
||||
references:
|
||||
- >-
|
||||
/home/sudacode/projects/japanese/SubMiner/src/main/character-dictionary-runtime.ts
|
||||
- >-
|
||||
/home/sudacode/projects/japanese/SubMiner/src/main/runtime/character-dictionary-auto-sync.ts
|
||||
- >-
|
||||
/home/sudacode/projects/japanese/SubMiner/src/config/definitions/defaults-integrations.ts
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Replace TTL-based per-anime character dictionary imports with a single merged Yomitan dictionary built from locally stored per-media metadata snapshots. Retain only most-recently-used anime up to configured maxLoaded, rebuild merged import when retained set membership/order changes, and avoid rebuilding on revisits that do not change the retained set.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Character dictionary retention becomes usage-based rather than TTL-based.
|
||||
- [x] #2 Only one Yomitan character dictionary import is maintained and updated as a merged dictionary.
|
||||
- [x] #3 Local storage keeps only metadata/snapshots needed to rebuild the merged dictionary; per-anime source zip cache is removed.
|
||||
- [x] #4 Merged dictionary rebuild occurs when retained-set membership or order changes, not on unchanged revisits.
|
||||
- [x] #5 Tests cover merged rebuild, MRU eviction, and no-op revisits.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Replaced per-media auto-sync imports with one merged Yomitan dictionary. Added snapshot persistence in `src/main/character-dictionary-runtime.ts` so auto-sync stores normalized per-media term/image metadata locally under `character-dictionaries/snapshots/` and rebuilds `merged.zip` from the MRU retained media ids.
|
||||
|
||||
Updated `src/main/runtime/character-dictionary-auto-sync.ts` to keep only MRU `activeMediaIds` plus merged revision/title state, rebuild/import the merged dictionary only when retained-set membership/order changes or the merged import is missing/stale, and skip rebuild on unchanged revisits.
|
||||
|
||||
Kept manual `generateForCurrentMedia` support by generating a one-off per-media zip from the stored snapshot, but removed the old per-media zip cache path from auto-sync state.
|
||||
|
||||
Updated config/help text to describe usage-based merged retention and mark legacy TTL/eviction knobs as ignored.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Implemented MRU-based merged character dictionary sync. Auto-sync now stores per-media normalized snapshots locally, rebuilds a single merged Yomitan dictionary when the retained anime set/order changes, and keeps `maxLoaded` as the cap on most-recently-used anime included in that merged import. Unchanged revisits no longer rebuild/import the dictionary.
|
||||
|
||||
Validation: `bun test src/main/runtime/character-dictionary-auto-sync.test.ts src/main/character-dictionary-runtime.test.ts`, `bun run tsc --noEmit`.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: TASK-90
|
||||
title: Expand TypeScript typecheck coverage beyond src
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-06 08:18'
|
||||
updated_date: '2026-03-06 08:23'
|
||||
labels:
|
||||
- tooling
|
||||
- typescript
|
||||
dependencies: []
|
||||
references:
|
||||
- /home/sudacode/projects/japanese/SubMiner/tsconfig.json
|
||||
- /home/sudacode/projects/japanese/SubMiner/package.json
|
||||
- /home/sudacode/projects/japanese/SubMiner/launcher
|
||||
- /home/sudacode/projects/japanese/SubMiner/scripts
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Bring all repository TypeScript entrypoints outside src/ into the enforced typecheck gate so CI and local checks cover launcher/ and script files, then resolve any surfaced diagnostics.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 TypeScript typecheck covers repository TypeScript entrypoints outside src/ that should be maintained in this repo, including launcher/ and script files.
|
||||
- [x] #2 The enforced typecheck command used by CI and local development passes with the expanded coverage.
|
||||
- [x] #3 Any diagnostics surfaced by the expanded coverage are fixed without weakening existing strictness for src/.
|
||||
- [x] #4 Relevant documentation or command wiring is updated if the typecheck entrypoint changes.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Added a dedicated repo-wide typecheck config at tsconfig.typecheck.json and wired package.json/CI to use `bun run typecheck` for launcher and scripts coverage without changing the existing src build config. Fixed the strict-null/indexing diagnostics surfaced in launcher/* and scripts/*, keeping src strictness intact. Verified with `bun run typecheck`, `bun run tsc --noEmit`, and `bun run test:launcher:src` (47 passing, plugin start gate OK).
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,35 @@
|
||||
---
|
||||
id: TASK-91
|
||||
title: >-
|
||||
Keep unsupported subtitle characters visible while excluding them from token
|
||||
hover
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-06 08:29'
|
||||
updated_date: '2026-03-06 08:32'
|
||||
labels:
|
||||
- bug
|
||||
- tokenizer
|
||||
- renderer
|
||||
dependencies: []
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Tokenizer/rendering bug: symbols and other unsupported characters with no lookup result are removed from the rendered subtitle line after tokenization, causing the displayed line to diverge from the source subtitle text. Update rendering so unsupported spans remain visible as plain text but are not tokenized/hoverable, and add regression coverage.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Subtitle rendering preserves unsupported symbols and special characters from the original line.
|
||||
- [x] #2 Unsupported symbols and special characters do not create interactive token hover targets.
|
||||
- [x] #3 Regression tests cover a mixed line containing tokenizable text plus unsupported characters.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Updated tokenized subtitle rendering to preserve unsupported punctuation and symbol spans as plain text while keeping only matched tokens interactive. Added renderer and alignment regression coverage for mixed lines so hover offsets stay correct after non-tokenizable characters remain visible.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
id: TASK-92
|
||||
title: Fix merged Yomitan headword selection for katakana subtitle tokens
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-06 08:43'
|
||||
updated_date: '2026-03-06 08:43'
|
||||
labels:
|
||||
- bug
|
||||
- tokenizer
|
||||
- yomitan
|
||||
dependencies: []
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Tokenizer/parser-selection bug: when a scanning-parser line is merged from multiple segments, the merged token currently keeps the first segment headword even if a later segment provides the full dictionary-backed term. This truncates katakana names such as バニール to バニ in the lookup payload and prevents correct dictionary matching. Also align kana classification so the prolonged sound mark is treated as kana in tokenizer heuristics.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Merged scanning-parser tokens prefer a full cross-segment headword when one segment expands to the full term.
|
||||
- [x] #2 Standalone later segment headwords do not override the primary token headword in normal content-word + auxiliary merges.
|
||||
- [x] #3 Katakana prolonged sound mark is treated as kana in tokenizer heuristics.
|
||||
- [x] #4 Regression tests cover the merged katakana headword case.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Adjusted merged scanning-parser headword selection so later segments only override the first headword when they provide an expanded cross-segment dictionary term, which fixes truncated katakana lookups like バニール -> バニ. Also updated kana classification to include the katakana prolonged sound mark and added regression coverage for both the expanded-headword case and the normal content-word-plus-auxiliary case.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
id: TASK-93
|
||||
title: Replace subtitle tokenizer with left-to-right Yomitan scanning parser
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-06 09:02'
|
||||
updated_date: '2026-03-06 09:14'
|
||||
labels:
|
||||
- tokenizer
|
||||
- yomitan
|
||||
- refactor
|
||||
dependencies: []
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Replace the current parseText candidate-selection tokenizer with a GSM-style left-to-right Yomitan scanning tokenizer for all subtitles. Preserve downstream token contracts for rendering, JLPT/frequency/N+1 annotation, and MeCab enrichment while improving full-term matching for names and katakana compounds.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Subtitle tokenization uses a left-to-right Yomitan scanning strategy instead of parseText candidate selection.
|
||||
- [x] #2 Token surfaces, readings, headwords, and offsets remain compatible with existing renderer and annotation stages.
|
||||
- [x] #3 Known problematic name cases such as カズマ and バニール resolve to full-token dictionary matches when Yomitan can match them.
|
||||
- [x] #4 Regression tests cover left-to-right exact-match scanning, unmatched text handling, and downstream tokenizeSubtitle integration.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Replaced the live subtitle tokenization path with a left-to-right Yomitan `termsFind` scanner that greedily advances through the normalized subtitle text, preserving downstream `MergedToken` contracts for renderer, MeCab enrichment, JLPT, frequency, and N+1 annotation. Added runtime and integration coverage for exact-match scanning plus name cases like カズマ and kept compatibility fallback handling for older mocked parseText-style test payloads.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,40 @@
|
||||
---
|
||||
id: TASK-94
|
||||
title: Add kana aliases for AniList character dictionary entries
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-06 09:20'
|
||||
updated_date: '2026-03-06 09:23'
|
||||
labels:
|
||||
- dictionary
|
||||
- tokenizer
|
||||
- anilist
|
||||
dependencies: []
|
||||
references:
|
||||
- >-
|
||||
/home/sudacode/projects/japanese/SubMiner/src/main/character-dictionary-runtime.ts
|
||||
- >-
|
||||
/home/sudacode/projects/japanese/SubMiner/src/main/character-dictionary-runtime.test.ts
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Generate katakana/hiragana-friendly aliases from AniList romanized character names so subtitle katakana names like カズマ match character dictionary entries even when AniList native name is kanji.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 AniList character dictionary generation adds kana aliases for romanized names when native name is not already kana-only
|
||||
- [x] #2 Generated dictionary entries allow katakana subtitle names like カズマ to resolve against a kanji-native AniList character entry
|
||||
- [x] #3 Regression tests cover alias generation and resulting term bank output
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Added katakana aliases synthesized from AniList romanized character names during character dictionary generation, so kanji-native entries such as 佐藤和真 / Satou Kazuma now also emit terms like カズマ and サトウカズマ with hiragana readings. Added regression coverage verifying generated term-bank output for the Konosuba case.
|
||||
|
||||
Verified with `bun test src/main/character-dictionary-runtime.test.ts` and `bun run tsc --noEmit`.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: TASK-95
|
||||
title: Invalidate old character dictionary snapshots after kana alias schema change
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-06 09:25'
|
||||
updated_date: '2026-03-06 09:28'
|
||||
labels:
|
||||
- dictionary
|
||||
- cache
|
||||
dependencies: []
|
||||
references:
|
||||
- >-
|
||||
/home/sudacode/projects/japanese/SubMiner/src/main/character-dictionary-runtime.ts
|
||||
- >-
|
||||
/home/sudacode/projects/japanese/SubMiner/src/main/character-dictionary-runtime.test.ts
|
||||
priority: high
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Bump character dictionary snapshot format/version so cached AniList snapshots created before kana alias generation are rebuilt automatically on next auto-sync or generation run.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Old cached character dictionary snapshots are treated as invalid after the schema/version bump
|
||||
- [x] #2 Current snapshot generation tests cover rebuild behavior across version mismatch
|
||||
- [x] #3 No manual cache deletion is required for users to pick up kana alias term generation
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Bumped the character dictionary snapshot format version so cached AniList snapshots created before kana alias generation are automatically treated as stale and rebuilt. Added regression coverage that seeds an older-format snapshot and verifies `getOrCreateCurrentSnapshot` fetches fresh data and overwrites the stale cache.
|
||||
|
||||
Verified with `bun test src/main/character-dictionary-runtime.test.ts` and `bun run tsc --noEmit`.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,47 @@
|
||||
---
|
||||
id: TASK-96
|
||||
title: Add launcher/app log progress for anime dictionary generate/update flow
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-06 09:30'
|
||||
updated_date: '2026-03-06 09:33'
|
||||
labels:
|
||||
- logging
|
||||
- dictionary
|
||||
- launcher
|
||||
dependencies: []
|
||||
references:
|
||||
- >-
|
||||
/home/sudacode/projects/japanese/SubMiner/src/main/character-dictionary-runtime.ts
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/core/services/cli-command.ts
|
||||
- >-
|
||||
/home/sudacode/projects/japanese/SubMiner/launcher/commands/playback-command.ts
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Surface user-visible log progress while the anime character dictionary is being generated or refreshed so launcher/app output no longer appears hung before mpv launches.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Dictionary generation logs a start/progress message before the first AniList/network/cache work begins.
|
||||
- [x] #2 Dictionary refresh/update path logs progress messages during the wait before completion.
|
||||
- [x] #3 Regression coverage verifies the new progress logging behavior.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Added progress logging to character dictionary generation at anime resolution, AniList match, snapshot miss, character-page fetch, image download start, and ZIP build stages.
|
||||
|
||||
Added auto-sync progress logging at snapshot sync start, active AniList set selection, merged rebuild, Yomitan import, and settings application stages.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Character dictionary generation/update no longer appears hung before mpv resumes. Added runtime progress logs for anime resolution, AniList lookup, snapshot rebuild, image-download phase, ZIP build, and auto-sync merged-dictionary import/settings stages. Added regression coverage in the runtime and auto-sync test suites and verified with focused Bun tests.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,39 @@
|
||||
---
|
||||
id: TASK-97
|
||||
title: Add configurable character-name token highlighting
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-06 10:15'
|
||||
updated_date: '2026-03-06 10:15'
|
||||
labels:
|
||||
- subtitle
|
||||
- dictionary
|
||||
- renderer
|
||||
dependencies: []
|
||||
references:
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/core/services/tokenizer.ts
|
||||
- >-
|
||||
/home/sudacode/projects/japanese/SubMiner/src/core/services/tokenizer/yomitan-parser-runtime.ts
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/renderer/subtitle-render.ts
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Color subtitle tokens that match entries from the SubMiner character dictionary, with a configurable default color and a config toggle that disables both rendering and name-match detection work.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Tokens matched from the SubMiner character dictionary receive dedicated renderer styling.
|
||||
- [x] #2 `subtitleStyle.nameMatchEnabled` disables name-match detection work when false.
|
||||
- [x] #3 `subtitleStyle.nameMatchColor` overrides the default `#f5bde6`.
|
||||
- [x] #4 Regression coverage verifies config parsing, tokenizer propagation, scanner gating, and renderer class/CSS behavior.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Added configurable character-name token highlighting with default color `#f5bde6` and config gate `subtitleStyle.nameMatchEnabled`. When enabled, left-to-right Yomitan scanning tags tokens whose winning dictionary entry comes from the SubMiner character dictionary; when disabled, the tokenizer skips that metadata work and the renderer suppresses name-match styling. Added focused regression tests for config parsing, main-deps wiring, Yomitan scan gating, token propagation, renderer classes, and CSS behavior.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
id: TASK-98
|
||||
title: Gate subtitle character-name highlighting on character dictionary enablement
|
||||
status: Done
|
||||
assignee:
|
||||
- codex
|
||||
created_date: '2026-03-07 00:54'
|
||||
updated_date: '2026-03-07 00:56'
|
||||
labels:
|
||||
- subtitle
|
||||
- character-dictionary
|
||||
dependencies: []
|
||||
references:
|
||||
- /Users/sudacode/projects/japanese/SubMiner/src/main.ts
|
||||
- /Users/sudacode/projects/japanese/SubMiner/src/core/services/tokenizer.ts
|
||||
- >-
|
||||
/Users/sudacode/projects/japanese/SubMiner/src/config/definitions/defaults-subtitle.ts
|
||||
priority: medium
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Ensure subtitle tokenization and other annotations continue to work, but character-name lookup/highlighting is disabled whenever the AniList character dictionary feature is disabled. This avoids unnecessary name-match processing when the backing dictionary is unavailable.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 When anilist.characterDictionary.enabled is false, subtitle tokenization does not request character-name match metadata or highlight character names.
|
||||
- [x] #2 When anilist.characterDictionary.enabled is true and subtitleStyle.nameMatchEnabled is true, existing character-name matching behavior remains enabled.
|
||||
- [x] #3 Subtitle tokenization, JLPT, frequency, and other non-name annotation behavior remain unchanged when character dictionaries are disabled.
|
||||
- [x] #4 Automated tests cover the runtime gating behavior.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
<!-- SECTION:PLAN:BEGIN -->
|
||||
1. Add a failing test in `src/main/runtime/subtitle-tokenization-main-deps.test.ts` proving name-match enablement resolves to false when `anilist.characterDictionary.enabled` is false even if `subtitleStyle.nameMatchEnabled` is true.
|
||||
2. Update `src/main/runtime/subtitle-tokenization-main-deps.ts` and `src/main.ts` so subtitle tokenization only enables name matching when both the subtitle setting and the character dictionary setting are enabled.
|
||||
3. Run focused Bun tests for the updated runtime deps and subtitle processing seams.
|
||||
4. If verification stays green, check off acceptance criteria and record the result.
|
||||
|
||||
Implementation plan saved in `docs/plans/2026-03-06-character-name-gating.md`.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
<!-- SECTION:NOTES:BEGIN -->
|
||||
Created plan doc `docs/plans/2026-03-06-character-name-gating.md` after user approved the narrow runtime-gating approach. Proceeding with TDD from the subtitle tokenization main-deps seam.
|
||||
|
||||
Implemented the gate at the subtitle tokenization runtime-deps boundary so `getNameMatchEnabled` is false unless both `subtitleStyle.nameMatchEnabled` and `anilist.characterDictionary.enabled` are true.
|
||||
|
||||
Verification: `bun test src/main/runtime/subtitle-tokenization-main-deps.test.ts`, `bun test src/core/services/subtitle-processing-controller.test.ts`, `bun run typecheck`.
|
||||
<!-- SECTION:NOTES:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Character-name lookup/highlighting is now suppressed when the AniList character dictionary is disabled, while subtitle tokenization and other annotation paths remain active. Added focused runtime-deps coverage and wired the main runtime to pass the character-dictionary enabled flag into subtitle tokenization.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -0,0 +1,38 @@
|
||||
---
|
||||
id: TASK-99
|
||||
title: Add configurable character dictionary collapsible section open states
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-07 00:00'
|
||||
updated_date: '2026-03-07 00:00'
|
||||
labels:
|
||||
- dictionary
|
||||
- config
|
||||
references:
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/main/character-dictionary-runtime.ts
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/config/resolve/integrations.ts
|
||||
- /home/sudacode/projects/japanese/SubMiner/src/config/definitions/defaults-integrations.ts
|
||||
priority: medium
|
||||
dependencies: []
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
Add per-section config for character dictionary collapsible glossary sections so Description, Character Information, and Voiced by can each default open or closed independently. Default all sections closed.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
<!-- AC:BEGIN -->
|
||||
- [x] #1 Config supports `anilist.characterDictionary.collapsibleSections.description`.
|
||||
- [x] #2 Config supports `anilist.characterDictionary.collapsibleSections.characterInformation`.
|
||||
- [x] #3 Config supports `anilist.characterDictionary.collapsibleSections.voicedBy`.
|
||||
- [x] #4 Default config keeps all generated character dictionary collapsible sections closed.
|
||||
- [x] #5 Regression coverage verifies config parsing/warnings and generated glossary `details.open` behavior.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
Added per-section open-state config under `anilist.characterDictionary.collapsibleSections` for `description`, `characterInformation`, and `voicedBy`, all defaulting to `false`. Wired the glossary generator to read those settings so generated `details.open` matches config, and added regression coverage for defaults, parsing/warnings, registry exposure, and runtime glossary output.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
153
build/installer.nsh
Normal file
153
build/installer.nsh
Normal file
@@ -0,0 +1,153 @@
|
||||
!include "MUI2.nsh"
|
||||
!include "nsDialogs.nsh"
|
||||
|
||||
Var WindowsMpvShortcutStartMenuPath
|
||||
Var WindowsMpvShortcutDesktopPath
|
||||
|
||||
!macro ResolveWindowsMpvShortcutPaths
|
||||
!ifdef MENU_FILENAME
|
||||
StrCpy $WindowsMpvShortcutStartMenuPath "$SMPROGRAMS\${MENU_FILENAME}\SubMiner mpv.lnk"
|
||||
!else
|
||||
StrCpy $WindowsMpvShortcutStartMenuPath "$SMPROGRAMS\SubMiner mpv.lnk"
|
||||
!endif
|
||||
StrCpy $WindowsMpvShortcutDesktopPath "$DESKTOP\SubMiner mpv.lnk"
|
||||
!macroend
|
||||
|
||||
!ifndef BUILD_UNINSTALLER
|
||||
Var WindowsMpvShortcutStartMenuCheckbox
|
||||
Var WindowsMpvShortcutDesktopCheckbox
|
||||
Var WindowsMpvShortcutStartMenuEnabled
|
||||
Var WindowsMpvShortcutDesktopEnabled
|
||||
Var WindowsMpvShortcutDefaultsInitialized
|
||||
|
||||
!macro customInit
|
||||
StrCpy $WindowsMpvShortcutStartMenuEnabled "1"
|
||||
StrCpy $WindowsMpvShortcutDesktopEnabled "1"
|
||||
StrCpy $WindowsMpvShortcutDefaultsInitialized "0"
|
||||
!macroend
|
||||
|
||||
!macro customPageAfterChangeDir
|
||||
PageEx custom
|
||||
PageCallbacks WindowsMpvShortcutPageCreate WindowsMpvShortcutPageLeave
|
||||
Caption " "
|
||||
PageExEnd
|
||||
!macroend
|
||||
|
||||
Function HasExistingInstallation
|
||||
ReadRegStr $0 SHELL_CONTEXT "Software\${APP_GUID}" InstallLocation
|
||||
${if} $0 == ""
|
||||
Push "0"
|
||||
${else}
|
||||
Push "1"
|
||||
${endif}
|
||||
FunctionEnd
|
||||
|
||||
Function InitializeWindowsMpvShortcutDefaults
|
||||
${if} $WindowsMpvShortcutDefaultsInitialized == "1"
|
||||
Return
|
||||
${endif}
|
||||
|
||||
!insertmacro ResolveWindowsMpvShortcutPaths
|
||||
Call HasExistingInstallation
|
||||
Pop $0
|
||||
|
||||
${if} $0 == "1"
|
||||
${if} ${FileExists} "$WindowsMpvShortcutStartMenuPath"
|
||||
StrCpy $WindowsMpvShortcutStartMenuEnabled "1"
|
||||
${else}
|
||||
StrCpy $WindowsMpvShortcutStartMenuEnabled "0"
|
||||
${endif}
|
||||
|
||||
${if} ${FileExists} "$WindowsMpvShortcutDesktopPath"
|
||||
StrCpy $WindowsMpvShortcutDesktopEnabled "1"
|
||||
${else}
|
||||
StrCpy $WindowsMpvShortcutDesktopEnabled "0"
|
||||
${endif}
|
||||
${else}
|
||||
StrCpy $WindowsMpvShortcutStartMenuEnabled "1"
|
||||
StrCpy $WindowsMpvShortcutDesktopEnabled "1"
|
||||
${endif}
|
||||
|
||||
StrCpy $WindowsMpvShortcutDefaultsInitialized "1"
|
||||
FunctionEnd
|
||||
|
||||
Function WindowsMpvShortcutPageCreate
|
||||
Call InitializeWindowsMpvShortcutDefaults
|
||||
|
||||
!insertmacro MUI_HEADER_TEXT "Windows mpv launcher" "Choose where to create the optional SubMiner mpv shortcuts."
|
||||
|
||||
nsDialogs::Create 1018
|
||||
Pop $0
|
||||
|
||||
${NSD_CreateLabel} 0u 0u 300u 30u "SubMiner mpv launches SubMiner.exe --launch-mpv so people can open mpv with the SubMiner profile from a separate Windows shortcut."
|
||||
Pop $0
|
||||
|
||||
${NSD_CreateCheckbox} 0u 44u 280u 12u "Create Start Menu shortcut"
|
||||
Pop $WindowsMpvShortcutStartMenuCheckbox
|
||||
${if} $WindowsMpvShortcutStartMenuEnabled == "1"
|
||||
${NSD_Check} $WindowsMpvShortcutStartMenuCheckbox
|
||||
${endif}
|
||||
|
||||
${NSD_CreateCheckbox} 0u 64u 280u 12u "Create Desktop shortcut"
|
||||
Pop $WindowsMpvShortcutDesktopCheckbox
|
||||
${if} $WindowsMpvShortcutDesktopEnabled == "1"
|
||||
${NSD_Check} $WindowsMpvShortcutDesktopCheckbox
|
||||
${endif}
|
||||
|
||||
${NSD_CreateLabel} 0u 90u 300u 24u "Upgrades preserve the current SubMiner mpv shortcut locations instead of recreating shortcuts you already removed."
|
||||
Pop $0
|
||||
|
||||
nsDialogs::Show
|
||||
FunctionEnd
|
||||
|
||||
Function WindowsMpvShortcutPageLeave
|
||||
${NSD_GetState} $WindowsMpvShortcutStartMenuCheckbox $0
|
||||
${if} $0 == ${BST_CHECKED}
|
||||
StrCpy $WindowsMpvShortcutStartMenuEnabled "1"
|
||||
${else}
|
||||
StrCpy $WindowsMpvShortcutStartMenuEnabled "0"
|
||||
${endif}
|
||||
|
||||
${NSD_GetState} $WindowsMpvShortcutDesktopCheckbox $0
|
||||
${if} $0 == ${BST_CHECKED}
|
||||
StrCpy $WindowsMpvShortcutDesktopEnabled "1"
|
||||
${else}
|
||||
StrCpy $WindowsMpvShortcutDesktopEnabled "0"
|
||||
${endif}
|
||||
FunctionEnd
|
||||
|
||||
!macro customInstall
|
||||
Call InitializeWindowsMpvShortcutDefaults
|
||||
!insertmacro ResolveWindowsMpvShortcutPaths
|
||||
|
||||
${if} $WindowsMpvShortcutStartMenuEnabled == "1"
|
||||
!ifdef MENU_FILENAME
|
||||
CreateDirectory "$SMPROGRAMS\${MENU_FILENAME}"
|
||||
!endif
|
||||
CreateShortCut "$WindowsMpvShortcutStartMenuPath" "$appExe" "--launch-mpv" "$appExe" 0 "" "" "Launch mpv with the SubMiner profile"
|
||||
# electron-builder's upstream NSIS templates use the same WinShell call for AppUserModelID wiring.
|
||||
# WinShell.dll comes from electron-builder's cached nsis-resources bundle, so bun run build:win needs no extra repo-local setup.
|
||||
ClearErrors
|
||||
WinShell::SetLnkAUMI "$WindowsMpvShortcutStartMenuPath" "${APP_ID}"
|
||||
${else}
|
||||
Delete "$WindowsMpvShortcutStartMenuPath"
|
||||
${endif}
|
||||
|
||||
${if} $WindowsMpvShortcutDesktopEnabled == "1"
|
||||
CreateShortCut "$WindowsMpvShortcutDesktopPath" "$appExe" "--launch-mpv" "$appExe" 0 "" "" "Launch mpv with the SubMiner profile"
|
||||
# ClearErrors keeps the optional AUMI assignment non-fatal if the packaging environment is missing WinShell.
|
||||
ClearErrors
|
||||
WinShell::SetLnkAUMI "$WindowsMpvShortcutDesktopPath" "${APP_ID}"
|
||||
${else}
|
||||
Delete "$WindowsMpvShortcutDesktopPath"
|
||||
${endif}
|
||||
|
||||
System::Call 'Shell32::SHChangeNotify(i 0x8000000, i 0, i 0, i 0)'
|
||||
!macroend
|
||||
!endif
|
||||
|
||||
!macro customUnInstall
|
||||
!insertmacro ResolveWindowsMpvShortcutPaths
|
||||
Delete "$WindowsMpvShortcutStartMenuPath"
|
||||
Delete "$WindowsMpvShortcutDesktopPath"
|
||||
!macroend
|
||||
21
build/signpath-windows-artifact-config.xml
Normal file
21
build/signpath-windows-artifact-config.xml
Normal file
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<artifact-configuration xmlns="http://signpath.io/artifact-configuration/v1">
|
||||
<zip-file>
|
||||
<pe-file path="SubMiner-*.exe" max-matches="unbounded">
|
||||
<authenticode-sign />
|
||||
</pe-file>
|
||||
<zip-file path="SubMiner-*.zip" max-matches="unbounded">
|
||||
<directory path="*">
|
||||
<pe-file path="*.exe" max-matches="unbounded">
|
||||
<authenticode-sign />
|
||||
</pe-file>
|
||||
<pe-file path="*.dll" max-matches="unbounded">
|
||||
<authenticode-sign />
|
||||
</pe-file>
|
||||
<pe-file path="*.node" max-matches="unbounded">
|
||||
<authenticode-sign />
|
||||
</pe-file>
|
||||
</directory>
|
||||
</zip-file>
|
||||
</zip-file>
|
||||
</artifact-configuration>
|
||||
21
changes/README.md
Normal file
21
changes/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Changelog Fragments
|
||||
|
||||
Add one `.md` file per user-visible PR in this directory.
|
||||
|
||||
Use this format:
|
||||
|
||||
```md
|
||||
type: added
|
||||
area: overlay
|
||||
|
||||
- Added keyboard navigation for Yomitan popups.
|
||||
- Added auto-pause toggle when opening the popup.
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- `type` required: `added`, `changed`, `fixed`, `docs`, or `internal`
|
||||
- `area` required: short product area like `overlay`, `launcher`, `release`
|
||||
- each non-empty body line becomes a bullet
|
||||
- `README.md` is ignored by the generator
|
||||
- if a PR should not produce release notes, apply the `skip-changelog` label instead of adding a fragment
|
||||
@@ -13,11 +13,12 @@
|
||||
|
||||
// ==========================================
|
||||
// Texthooker Server
|
||||
// Control whether browser opens automatically for texthooker.
|
||||
// Configure texthooker startup launch and browser opening behavior.
|
||||
// ==========================================
|
||||
"texthooker": {
|
||||
"launchAtStartup": true, // Launch texthooker server automatically when SubMiner starts. Values: true | false
|
||||
"openBrowser": true, // Open browser setting. Values: true | false
|
||||
}, // Control whether browser opens automatically for texthooker.
|
||||
}, // Configure texthooker startup launch and browser opening behavior.
|
||||
|
||||
// ==========================================
|
||||
// WebSocket Server
|
||||
@@ -29,6 +30,16 @@
|
||||
"port": 6677, // Built-in subtitle websocket server port.
|
||||
}, // Built-in WebSocket server broadcasts subtitle text to connected clients.
|
||||
|
||||
// ==========================================
|
||||
// Annotation WebSocket
|
||||
// Dedicated annotated subtitle websocket for bundled texthooker and token-aware clients.
|
||||
// Independent from websocket.auto and defaults to port 6678.
|
||||
// ==========================================
|
||||
"annotationWebsocket": {
|
||||
"enabled": true, // Annotated subtitle websocket server enabled state. Values: true | false
|
||||
"port": 6678, // Annotated subtitle websocket server port.
|
||||
}, // Dedicated annotated subtitle websocket for bundled texthooker and token-aware clients.
|
||||
|
||||
// ==========================================
|
||||
// Logging
|
||||
// Controls logging verbosity.
|
||||
@@ -36,7 +47,21 @@
|
||||
// ==========================================
|
||||
"logging": {
|
||||
"level": "info", // Minimum log level for runtime logging. Values: debug | info | warn | error
|
||||
}, // Controls logging verbosity. Keep this as an object; do not replace with a bare string.
|
||||
}, // Controls logging verbosity.
|
||||
|
||||
// ==========================================
|
||||
// Startup Warmups
|
||||
// Background warmup controls for MeCab, Yomitan, dictionaries, and Jellyfin session.
|
||||
// Disable individual warmups to defer load until first real usage.
|
||||
// lowPowerMode defers all warmups except Yomitan extension.
|
||||
// ==========================================
|
||||
"startupWarmups": {
|
||||
"lowPowerMode": false, // Defer startup warmups except Yomitan extension. Values: true | false
|
||||
"mecab": true, // Warm up MeCab tokenizer at startup. Values: true | false
|
||||
"yomitanExtension": true, // Warm up Yomitan extension at startup. Values: true | false
|
||||
"subtitleDictionaries": true, // Warm up subtitle dictionaries at startup. Values: true | false
|
||||
"jellyfinRemoteSession": true, // Warm up Jellyfin remote session at startup. Values: true | false
|
||||
}, // Background warmup controls for MeCab, Yomitan, dictionaries, and Jellyfin session.
|
||||
|
||||
// ==========================================
|
||||
// Keyboard Shortcuts
|
||||
@@ -88,6 +113,7 @@
|
||||
"alass_path": "", // Alass path setting.
|
||||
"ffsubsync_path": "", // Ffsubsync path setting.
|
||||
"ffmpeg_path": "", // Ffmpeg path setting.
|
||||
"replace": true, // Replace the active subtitle file when sync completes. Values: true | false
|
||||
}, // Subsync engine and executable paths.
|
||||
|
||||
// ==========================================
|
||||
@@ -107,8 +133,11 @@
|
||||
"enableJlpt": false, // Enable JLPT vocabulary level underlines. When disabled, JLPT tagging lookup and underlines are skipped. Values: true | false
|
||||
"preserveLineBreaks": false, // Preserve line breaks in visible overlay subtitle rendering. When false, line breaks are flattened to spaces for a single-line flow. Values: true | false
|
||||
"autoPauseVideoOnHover": true, // Automatically pause mpv playback while hovering subtitle text, then resume on leave. Values: true | false
|
||||
"autoPauseVideoOnYomitanPopup": false, // Automatically pause mpv playback while Yomitan popup is open, then resume when popup closes. Values: true | false
|
||||
"hoverTokenColor": "#f4dbd6", // Hex color used for hovered subtitle token highlight in mpv.
|
||||
"hoverTokenBackgroundColor": "rgba(54, 58, 79, 0.84)", // CSS color used for hovered subtitle token background highlight in mpv.
|
||||
"nameMatchEnabled": true, // Enable subtitle token coloring for matches from the SubMiner character dictionary. Values: true | false
|
||||
"nameMatchColor": "#f5bde6", // Hex color used when a subtitle token matches an entry from the SubMiner character dictionary.
|
||||
"fontFamily": "M PLUS 1 Medium, Source Han Sans JP, Noto Sans CJK JP", // Font family setting.
|
||||
"fontSize": 35, // Font size setting.
|
||||
"fontColor": "#cad3f5", // Font color setting.
|
||||
@@ -133,12 +162,12 @@
|
||||
}, // Jlpt colors setting.
|
||||
"frequencyDictionary": {
|
||||
"enabled": false, // Enable frequency-dictionary-based highlighting based on token rank. Values: true | false
|
||||
"sourcePath": "", // Optional absolute path to a frequency dictionary directory. If empty, SubMiner searches installed/default frequency-dictionary locations.
|
||||
"sourcePath": "", // Optional absolute path to a frequency dictionary directory. If empty, built-in discovery search paths are used.
|
||||
"topX": 1000, // Only color tokens with frequency rank <= topX (default: 1000).
|
||||
"mode": "single", // single: use one color for all matching tokens. banded: use color ramp by frequency band. Values: single | banded
|
||||
"matchMode": "headword", // Frequency lookup text selection mode. Values: headword | surface
|
||||
"matchMode": "headword", // headword: frequency lookup uses dictionary form. surface: lookup uses subtitle-visible token text. Values: headword | surface
|
||||
"singleColor": "#f5a97f", // Color used when frequencyDictionary.mode is `single`.
|
||||
"bandedColors": ["#ed8796", "#f5a97f", "#f9e2af", "#a6e3a1", "#8aadf4"], // Five colors used for rank bands when mode is `banded` (from most common to least within topX).
|
||||
"bandedColors": ["#ed8796", "#f5a97f", "#f9e2af", "#8bd5ca", "#8aadf4"], // Five colors used for rank bands when mode is `banded` (from most common to least within topX).
|
||||
}, // Frequency dictionary setting.
|
||||
"secondary": {
|
||||
"fontFamily": "Inter, Noto Sans, Helvetica Neue, sans-serif", // Font family setting.
|
||||
@@ -149,18 +178,31 @@
|
||||
"wordSpacing": 0, // Word spacing setting.
|
||||
"fontKerning": "normal", // Font kerning setting.
|
||||
"textRendering": "geometricPrecision", // Text rendering setting.
|
||||
"textShadow": "0 3px 10px rgba(0,0,0,0.69)", // Text shadow setting.
|
||||
"backgroundColor": "transparent", // Background color setting.
|
||||
"textShadow": "0 2px 4px rgba(0,0,0,0.95), 0 0 8px rgba(0,0,0,0.8), 0 0 16px rgba(0,0,0,0.55)", // Text shadow setting.
|
||||
"backgroundColor": "rgba(20, 22, 34, 0.78)", // Background color setting.
|
||||
"backdropFilter": "blur(6px)", // Backdrop filter setting.
|
||||
"fontWeight": "normal", // Font weight setting.
|
||||
"fontWeight": "600", // Font weight setting.
|
||||
"fontStyle": "normal", // Font style setting.
|
||||
}, // Secondary setting.
|
||||
}, // Primary and secondary subtitle styling.
|
||||
|
||||
// ==========================================
|
||||
// Shared AI Provider
|
||||
// Canonical OpenAI-compatible provider transport settings shared by Anki and YouTube subtitle fixing.
|
||||
// ==========================================
|
||||
"ai": {
|
||||
"enabled": false, // Enable shared OpenAI-compatible AI provider features. Values: true | false
|
||||
"apiKey": "", // Static API key for the shared OpenAI-compatible AI provider.
|
||||
"apiKeyCommand": "", // Shell command used to resolve the shared AI provider API key.
|
||||
"baseUrl": "https://openrouter.ai/api", // Base URL for the shared OpenAI-compatible AI provider.
|
||||
"requestTimeoutMs": 15000, // Timeout in milliseconds for shared AI provider requests.
|
||||
}, // Canonical OpenAI-compatible provider transport settings shared by Anki and YouTube subtitle fixing.
|
||||
|
||||
// ==========================================
|
||||
// AnkiConnect Integration
|
||||
// Automatic Anki updates and media generation options.
|
||||
// Hot-reload: AI translation settings update live while SubMiner is running.
|
||||
// Hot-reload: ankiConnect.ai.enabled updates live while SubMiner is running.
|
||||
// Shared AI provider transport settings are read from top-level ai and typically require restart.
|
||||
// Most other AnkiConnect settings still require restart.
|
||||
// ==========================================
|
||||
"ankiConnect": {
|
||||
@@ -168,7 +210,7 @@
|
||||
"url": "http://127.0.0.1:8765", // Url setting.
|
||||
"pollingRate": 3000, // Polling interval in milliseconds.
|
||||
"proxy": {
|
||||
"enabled": false, // Enable local AnkiConnect-compatible proxy for push-based auto-enrichment. Values: true | false
|
||||
"enabled": true, // Enable local AnkiConnect-compatible proxy for push-based auto-enrichment. Values: true | false
|
||||
"host": "127.0.0.1", // Bind host for local AnkiConnect proxy.
|
||||
"port": 8766, // Bind port for local AnkiConnect proxy.
|
||||
"upstreamUrl": "http://127.0.0.1:8765", // Upstream AnkiConnect URL proxied by local AnkiConnect proxy.
|
||||
@@ -182,13 +224,9 @@
|
||||
"translation": "SelectionText", // Translation setting.
|
||||
}, // Fields setting.
|
||||
"ai": {
|
||||
"enabled": false, // Enabled setting. Values: true | false
|
||||
"alwaysUseAiTranslation": false, // Always use ai translation setting. Values: true | false
|
||||
"apiKey": "", // Api key setting.
|
||||
"model": "openai/gpt-4o-mini", // Model setting.
|
||||
"baseUrl": "https://openrouter.ai/api", // Base url setting.
|
||||
"targetLanguage": "English", // Target language setting.
|
||||
"systemPrompt": "You are a translation engine. Return only the translated text with no explanations.", // System prompt setting.
|
||||
"enabled": false, // Enable AI provider usage for Anki translation/enrichment flows. Values: true | false
|
||||
"model": "", // Optional model override for Anki AI translation/enrichment flows.
|
||||
"systemPrompt": "", // Optional system prompt override for Anki AI translation/enrichment flows.
|
||||
}, // Ai setting.
|
||||
"media": {
|
||||
"generateAudio": true, // Generate audio setting. Values: true | false
|
||||
@@ -246,22 +284,42 @@
|
||||
|
||||
// ==========================================
|
||||
// YouTube Subtitle Generation
|
||||
// Defaults for subminer YouTube subtitle extraction/transcription mode.
|
||||
// Defaults for SubMiner YouTube subtitle generation.
|
||||
// ==========================================
|
||||
"youtubeSubgen": {
|
||||
"mode": "automatic", // YouTube subtitle generation mode for the launcher script. Values: automatic | preprocess | off
|
||||
"whisperBin": "", // Path to whisper.cpp CLI used as fallback transcription engine.
|
||||
"whisperModel": "", // Path to whisper model used for fallback transcription.
|
||||
"whisperVadModel": "", // Path to optional whisper VAD model used for subtitle generation.
|
||||
"whisperThreads": 4, // Thread count passed to whisper.cpp subtitle generation runs.
|
||||
"fixWithAi": false, // Use shared AI provider to post-process whisper-generated YouTube subtitles. Values: true | false
|
||||
"ai": {
|
||||
"model": "", // Optional model override for YouTube subtitle AI post-processing.
|
||||
"systemPrompt": "", // Optional system prompt override for YouTube subtitle AI post-processing.
|
||||
}, // Ai setting.
|
||||
"primarySubLanguages": ["ja", "jpn"], // Comma-separated primary subtitle language priority used by the launcher.
|
||||
}, // Defaults for subminer YouTube subtitle extraction/transcription mode.
|
||||
}, // Defaults for SubMiner YouTube subtitle generation.
|
||||
|
||||
// ==========================================
|
||||
// Anilist
|
||||
// Anilist API credentials and update behavior.
|
||||
// Includes optional auto-sync for a merged MRU-based character dictionary in bundled Yomitan.
|
||||
// Character dictionaries are keyed by AniList media ID (no season/franchise merge).
|
||||
// ==========================================
|
||||
"anilist": {
|
||||
"enabled": false, // Enable AniList post-watch progress updates. Values: true | false
|
||||
"accessToken": "", // Optional explicit AniList access token override; leave empty to use locally stored token from setup.
|
||||
"characterDictionary": {
|
||||
"enabled": false, // Enable automatic Yomitan character dictionary sync for currently watched AniList media. Values: true | false
|
||||
"refreshTtlHours": 168, // Legacy setting; merged character dictionary retention is now usage-based and this value is ignored.
|
||||
"maxLoaded": 3, // Maximum number of most-recently-used anime snapshots included in the merged Yomitan character dictionary.
|
||||
"evictionPolicy": "delete", // Legacy setting; merged character dictionary eviction is usage-based and this value is ignored. Values: disable | delete
|
||||
"profileScope": "all", // Yomitan profile scope for dictionary enable/disable updates. Values: all | active
|
||||
"collapsibleSections": {
|
||||
"description": false, // Open the Description section by default in character dictionary glossary entries. Values: true | false
|
||||
"characterInformation": false, // Open the Character Information section by default in character dictionary glossary entries. Values: true | false
|
||||
"voicedBy": false, // Open the Voiced by section by default in character dictionary glossary entries. Values: true | false
|
||||
}, // Collapsible sections setting.
|
||||
}, // Character dictionary setting.
|
||||
}, // Anilist API credentials and update behavior.
|
||||
|
||||
// ==========================================
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
const repositoryName = process.env.GITHUB_REPOSITORY?.split('/')[1];
|
||||
const base = process.env.GITHUB_ACTIONS && repositoryName ? `/${repositoryName}/` : '/';
|
||||
|
||||
export default {
|
||||
title: 'SubMiner Docs',
|
||||
description:
|
||||
'SubMiner: an MPV immersion-mining overlay with Yomitan and AnkiConnect integration.',
|
||||
base,
|
||||
head: [
|
||||
['link', { rel: 'icon', href: '/favicon.ico', sizes: 'any' }],
|
||||
[
|
||||
'link',
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
href: '/favicon-32x32.png',
|
||||
sizes: '32x32',
|
||||
},
|
||||
],
|
||||
[
|
||||
'link',
|
||||
{
|
||||
rel: 'icon',
|
||||
type: 'image/png',
|
||||
href: '/favicon-16x16.png',
|
||||
sizes: '16x16',
|
||||
},
|
||||
],
|
||||
[
|
||||
'link',
|
||||
{
|
||||
rel: 'apple-touch-icon',
|
||||
href: '/apple-touch-icon.png',
|
||||
sizes: '180x180',
|
||||
},
|
||||
],
|
||||
],
|
||||
appearance: 'dark',
|
||||
cleanUrls: true,
|
||||
metaChunk: true,
|
||||
sitemap: { hostname: 'https://docs.subminer.moe' },
|
||||
lastUpdated: true,
|
||||
srcExclude: ['subagents/**'],
|
||||
markdown: {
|
||||
theme: {
|
||||
light: 'catppuccin-latte',
|
||||
dark: 'catppuccin-macchiato',
|
||||
},
|
||||
},
|
||||
themeConfig: {
|
||||
logo: {
|
||||
light: '/assets/SubMiner.png',
|
||||
dark: '/assets/SubMiner.png',
|
||||
},
|
||||
siteTitle: 'SubMiner Docs',
|
||||
nav: [
|
||||
{ text: 'Home', link: '/' },
|
||||
{ text: 'Get Started', link: '/installation' },
|
||||
{ text: 'Mining', link: '/mining-workflow' },
|
||||
{ text: 'Configuration', link: '/configuration' },
|
||||
{ text: 'Troubleshooting', link: '/troubleshooting' },
|
||||
],
|
||||
sidebar: [
|
||||
{
|
||||
text: 'Getting Started',
|
||||
items: [
|
||||
{ text: 'Overview', link: '/' },
|
||||
{ text: 'Installation', link: '/installation' },
|
||||
{ text: 'Launcher Script', link: '/launcher-script' },
|
||||
{ text: 'Usage', link: '/usage' },
|
||||
{ text: 'Mining Workflow', link: '/mining-workflow' },
|
||||
// { text: 'Feature Demos', link: '/demos' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Reference',
|
||||
items: [
|
||||
{ text: 'Configuration', link: '/configuration' },
|
||||
{ text: 'Keyboard Shortcuts', link: '/shortcuts' },
|
||||
{ text: 'Anki Integration', link: '/anki-integration' },
|
||||
{ text: 'Jellyfin Integration', link: '/jellyfin-integration' },
|
||||
{ text: 'Immersion Tracking', link: '/immersion-tracking' },
|
||||
{ text: 'JLPT Vocabulary', link: '/jlpt-vocab-bundle' },
|
||||
{ text: 'MPV Plugin', link: '/mpv-plugin' },
|
||||
{ text: 'Troubleshooting', link: '/troubleshooting' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Development',
|
||||
items: [
|
||||
{ text: 'Building & Testing', link: '/development' },
|
||||
{ text: 'Architecture', link: '/architecture' },
|
||||
{ text: 'IPC + Runtime Contracts', link: '/ipc-contracts' },
|
||||
],
|
||||
},
|
||||
],
|
||||
search: {
|
||||
provider: 'local',
|
||||
},
|
||||
footer: {
|
||||
message: 'Released under the GPL-3.0 License.',
|
||||
copyright: 'Copyright © 2026-present sudacode',
|
||||
},
|
||||
editLink: {
|
||||
pattern: 'https://github.com/ksyasuda/SubMiner/edit/main/docs/:path',
|
||||
text: 'Edit this page on GitHub',
|
||||
},
|
||||
outline: { level: [2, 3], label: 'On this page' },
|
||||
externalLinkIcon: true,
|
||||
docFooter: { prev: 'Previous', next: 'Next' },
|
||||
returnToTopLabel: 'Back to top',
|
||||
socialLinks: [{ icon: 'github', link: 'https://github.com/ksyasuda/SubMiner' }],
|
||||
},
|
||||
};
|
||||
@@ -1,194 +0,0 @@
|
||||
import DefaultTheme from 'vitepress/theme';
|
||||
import { useRoute } from 'vitepress';
|
||||
import { nextTick, onMounted, watch } from 'vue';
|
||||
import '@catppuccin/vitepress/theme/macchiato/mauve.css';
|
||||
import './mermaid-modal.css';
|
||||
|
||||
let mermaidLoader: Promise<any> | null = null;
|
||||
const MERMAID_MODAL_ID = 'mermaid-diagram-modal';
|
||||
|
||||
function closeMermaidModal() {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = document.getElementById(MERMAID_MODAL_ID);
|
||||
if (!modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
modal.classList.remove('is-open');
|
||||
document.body.classList.remove('mermaid-modal-open');
|
||||
}
|
||||
|
||||
function ensureMermaidModal(): HTMLDivElement {
|
||||
const existing = document.getElementById(MERMAID_MODAL_ID);
|
||||
if (existing) {
|
||||
return existing as HTMLDivElement;
|
||||
}
|
||||
|
||||
const modal = document.createElement('div');
|
||||
modal.id = MERMAID_MODAL_ID;
|
||||
modal.className = 'mermaid-modal';
|
||||
modal.innerHTML = `
|
||||
<div class="mermaid-modal__backdrop" data-mermaid-close="true"></div>
|
||||
<div class="mermaid-modal__dialog" role="dialog" aria-modal="true" aria-label="Expanded Mermaid diagram">
|
||||
<button class="mermaid-modal__close" type="button" aria-label="Close Mermaid diagram">Close</button>
|
||||
<div class="mermaid-modal__content"></div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.addEventListener('click', (event) => {
|
||||
const target = event.target as HTMLElement | null;
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (target.closest('[data-mermaid-close="true"]') || target.closest('.mermaid-modal__close')) {
|
||||
closeMermaidModal();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Escape' && modal.classList.contains('is-open')) {
|
||||
closeMermaidModal();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.appendChild(modal);
|
||||
return modal;
|
||||
}
|
||||
|
||||
function openMermaidModal(sourceNode: HTMLElement) {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const modal = ensureMermaidModal();
|
||||
const content = modal.querySelector<HTMLDivElement>('.mermaid-modal__content');
|
||||
if (!content) {
|
||||
return;
|
||||
}
|
||||
|
||||
content.replaceChildren(sourceNode.cloneNode(true));
|
||||
modal.classList.add('is-open');
|
||||
document.body.classList.add('mermaid-modal-open');
|
||||
}
|
||||
|
||||
function attachMermaidInteractions(nodes: HTMLElement[]) {
|
||||
for (const node of nodes) {
|
||||
if (node.dataset.mermaidInteractive === 'true') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const svg = node.querySelector<HTMLElement>('svg');
|
||||
if (!svg) {
|
||||
continue;
|
||||
}
|
||||
|
||||
node.classList.add('mermaid-interactive');
|
||||
node.setAttribute('role', 'button');
|
||||
node.setAttribute('tabindex', '0');
|
||||
node.setAttribute('aria-label', 'Open Mermaid diagram in full view');
|
||||
|
||||
const open = () => openMermaidModal(svg);
|
||||
node.addEventListener('click', open);
|
||||
node.addEventListener('keydown', (event) => {
|
||||
if (event.key === 'Enter' || event.key === ' ') {
|
||||
event.preventDefault();
|
||||
open();
|
||||
}
|
||||
});
|
||||
|
||||
node.dataset.mermaidInteractive = 'true';
|
||||
}
|
||||
}
|
||||
|
||||
async function getMermaid() {
|
||||
if (!mermaidLoader) {
|
||||
mermaidLoader = import('mermaid').then((module) => {
|
||||
const mermaid = module.default;
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
securityLevel: 'loose',
|
||||
theme: 'base',
|
||||
themeVariables: {
|
||||
background: '#24273a',
|
||||
primaryColor: '#363a4f',
|
||||
primaryTextColor: '#cad3f5',
|
||||
primaryBorderColor: '#c6a0f6',
|
||||
secondaryColor: '#494d64',
|
||||
secondaryTextColor: '#cad3f5',
|
||||
secondaryBorderColor: '#b7bdf8',
|
||||
tertiaryColor: '#5b6078',
|
||||
tertiaryTextColor: '#cad3f5',
|
||||
tertiaryBorderColor: '#8aadf4',
|
||||
lineColor: '#939ab7',
|
||||
textColor: '#cad3f5',
|
||||
mainBkg: '#363a4f',
|
||||
nodeBorder: '#c6a0f6',
|
||||
clusterBkg: '#1e2030',
|
||||
clusterBorder: '#494d64',
|
||||
edgeLabelBackground: '#24273a',
|
||||
labelTextColor: '#cad3f5',
|
||||
},
|
||||
});
|
||||
return mermaid;
|
||||
});
|
||||
}
|
||||
return mermaidLoader;
|
||||
}
|
||||
|
||||
async function renderMermaidBlocks() {
|
||||
if (typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
const blocks = Array.from(document.querySelectorAll<HTMLElement>('div.language-mermaid'));
|
||||
if (blocks.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mermaid = await getMermaid();
|
||||
const nodes: HTMLElement[] = [];
|
||||
|
||||
for (const block of blocks) {
|
||||
if (block.dataset.mermaidRendered === 'true') {
|
||||
continue;
|
||||
}
|
||||
const code = block.querySelector('pre code');
|
||||
const source = code?.textContent?.trim();
|
||||
if (!source) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const mount = document.createElement('div');
|
||||
mount.className = 'mermaid';
|
||||
mount.textContent = source;
|
||||
|
||||
block.replaceChildren(mount);
|
||||
block.dataset.mermaidRendered = 'true';
|
||||
nodes.push(mount);
|
||||
}
|
||||
|
||||
if (nodes.length > 0) {
|
||||
await mermaid.run({ nodes });
|
||||
attachMermaidInteractions(nodes);
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
...DefaultTheme,
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const render = () => {
|
||||
nextTick(() => {
|
||||
renderMermaidBlocks().catch((error) => {
|
||||
console.error('Failed to render Mermaid diagram:', error);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(render);
|
||||
watch(() => route.path, render);
|
||||
},
|
||||
};
|
||||
@@ -1,69 +0,0 @@
|
||||
.mermaid-interactive {
|
||||
cursor: zoom-in;
|
||||
}
|
||||
|
||||
.mermaid-interactive:focus-visible {
|
||||
outline: 2px solid var(--vp-c-brand-1);
|
||||
outline-offset: 4px;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.mermaid-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 200;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mermaid-modal.is-open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mermaid-modal__backdrop {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.72);
|
||||
}
|
||||
|
||||
.mermaid-modal__dialog {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: 4vh auto;
|
||||
width: min(96vw, 1800px);
|
||||
max-height: 92vh;
|
||||
border: 1px solid var(--vp-c-border);
|
||||
border-radius: 12px;
|
||||
background: var(--vp-c-bg);
|
||||
box-shadow: var(--vp-shadow-4);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mermaid-modal__close {
|
||||
display: block;
|
||||
margin-left: auto;
|
||||
margin-right: 16px;
|
||||
margin-top: 12px;
|
||||
border: 1px solid var(--vp-c-border);
|
||||
border-radius: 6px;
|
||||
padding: 4px 10px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.mermaid-modal__content {
|
||||
overflow: auto;
|
||||
max-height: calc(92vh - 56px);
|
||||
padding: 8px 16px 16px;
|
||||
}
|
||||
|
||||
.mermaid-modal__content svg {
|
||||
max-width: none;
|
||||
width: max-content;
|
||||
height: auto;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
body.mermaid-modal-open {
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
# Documentation
|
||||
|
||||
SubMiner documentation is built with [VitePress](https://vitepress.dev/).
|
||||
|
||||
## Local Docs Site
|
||||
|
||||
```bash
|
||||
make docs-dev # Dev server at http://localhost:5173
|
||||
make docs # Build static output
|
||||
make docs-preview # Preview built site at http://localhost:4173
|
||||
```
|
||||
|
||||
## Pages
|
||||
|
||||
### Getting Started
|
||||
|
||||
- [Installation](/installation) — Requirements, Linux/macOS/Windows install, mpv plugin setup
|
||||
- [Usage](/usage) — `subminer` wrapper + subcommands (`jellyfin`, `yt`, `doctor`, `config`, `mpv`, `texthooker`, `app`), mpv plugin, keybindings
|
||||
- [Mining Workflow](/mining-workflow) — End-to-end sentence mining guide, single overlay + modals, card creation
|
||||
|
||||
### Reference
|
||||
|
||||
- [Configuration](/configuration) — Full config file reference and option details
|
||||
- [Keyboard Shortcuts](/shortcuts) — All global, overlay, mining, and plugin chord shortcuts in one place
|
||||
- [Anki Integration](/anki-integration) — AnkiConnect setup, proxy/polling transport, field mapping, media generation, field grouping
|
||||
- [Jellyfin Integration](/jellyfin-integration) — Optional Jellyfin auth, cast discovery, remote control, and playback launch
|
||||
- [Immersion Tracking](/immersion-tracking) — SQLite schema, retention/rollup policies, query templates, and extension points
|
||||
- [Performance & Tuning](/troubleshooting#performance-and-resource-impact) — Resource usage and practical low-impact profile
|
||||
- [JLPT Vocabulary](/jlpt-vocab-bundle) — Bundled term-meta bank for JLPT level underlining and frequency highlighting
|
||||
- [MPV Plugin](/mpv-plugin) — Chord keybindings, subminer.conf options, script messages
|
||||
- [Troubleshooting](/troubleshooting) — Common issues and solutions by category
|
||||
|
||||
### Development
|
||||
|
||||
- [Building & Testing](/development) — Build commands, test suites, contributor notes, environment variables
|
||||
- [Architecture](/architecture) — Service-oriented design, composition model, renderer module layout
|
||||
- [IPC + Runtime Contracts](/ipc-contracts) — Main/renderer IPC contracts and contributor onboarding
|
||||
@@ -1,346 +0,0 @@
|
||||
# Anki Integration
|
||||
|
||||
SubMiner uses the [AnkiConnect](https://ankiweb.net/shared/info/2055492159) add-on to create and update Anki cards with sentence context, audio, and screenshots.
|
||||
This project is built primarily for [Kiku](https://kiku.youyoumu.my.id/) and [Lapis](https://github.com/donkuri/lapis) note types, including sentence-card and field-grouping behavior.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Install [Anki](https://apps.ankiweb.net/).
|
||||
2. Install the [AnkiConnect](https://ankiweb.net/shared/info/2055492159) add-on (code: `2055492159`).
|
||||
3. Keep Anki running while using SubMiner.
|
||||
|
||||
AnkiConnect listens on `http://127.0.0.1:8765` by default. If you changed the port in AnkiConnect's settings, update `ankiConnect.url` in your SubMiner config.
|
||||
|
||||
## Auto-Enrichment Transport
|
||||
|
||||
SubMiner supports two auto-enrichment transport modes:
|
||||
|
||||
1. `proxy` (default): runs a local AnkiConnect-compatible proxy and enriches cards immediately after successful `addNote` / `addNotes` / `multi` responses.
|
||||
2. `polling`: polls AnkiConnect at `ankiConnect.pollingRate` (default: 3s).
|
||||
|
||||
In both modes, the enrichment workflow is the same:
|
||||
|
||||
1. Checks if a duplicate expression already exists (for field grouping).
|
||||
2. Updates the sentence field with the current subtitle.
|
||||
3. Generates and uploads audio and image media.
|
||||
4. Fills the translation field from the secondary subtitle or AI.
|
||||
5. Writes metadata to the miscInfo field.
|
||||
|
||||
Polling mode uses the query `"deck:<your-deck>" added:1` to find recently added cards. If no deck is configured, it searches all decks.
|
||||
|
||||
### Proxy Mode Setup (Yomitan / Texthooker)
|
||||
|
||||
```jsonc
|
||||
"ankiConnect": {
|
||||
"url": "http://127.0.0.1:8765", // real AnkiConnect
|
||||
"proxy": {
|
||||
"enabled": true,
|
||||
"host": "127.0.0.1",
|
||||
"port": 8766,
|
||||
"upstreamUrl": "http://127.0.0.1:8765"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then point Yomitan/clients to `http://127.0.0.1:8766` instead of `8765`.
|
||||
|
||||
When SubMiner loads the bundled Yomitan extension, it also attempts to update the **default Yomitan profile** (`profiles[0].options.anki.server`) to the active SubMiner endpoint:
|
||||
|
||||
- proxy URL when `ankiConnect.proxy.enabled` is `true`
|
||||
- direct `ankiConnect.url` when proxy mode is disabled
|
||||
|
||||
To avoid clobbering custom setups, this auto-update only changes the default profile when its current server is blank or the stock Yomitan default (`http://127.0.0.1:8765`).
|
||||
|
||||
For browser-based Yomitan or other external clients (for example Texthooker in a normal browser profile), set their Anki server to the same proxy URL separately: `http://127.0.0.1:8766` (or your configured `proxy.host` + `proxy.port`).
|
||||
|
||||
### Browser/Yomitan external setup (separate profile)
|
||||
|
||||
If you want SubMiner to use proxy mode without touching your main/default Yomitan profile, create or select a separate Yomitan profile just for SubMiner and set its Anki server to the proxy URL.
|
||||
|
||||
That profile isolation gives you both benefits:
|
||||
|
||||
- SubMiner can auto-enrich immediately via proxy.
|
||||
- Your default Yomitan profile keeps its existing Anki server setting.
|
||||
|
||||
In Yomitan, go to Settings → Profile and:
|
||||
|
||||
1. Create a profile for SubMiner (or choose one dedicated profile).
|
||||
2. Open Anki settings for that profile.
|
||||
3. Set server to `http://127.0.0.1:8766` (or your configured proxy URL).
|
||||
4. Save and make that profile active when using SubMiner.
|
||||
|
||||
This is only for non-bundled, external/browser Yomitan or other clients. The bundled profile auto-update logic only targets `profiles[0]` when it is blank or still default.
|
||||
|
||||
### Proxy Troubleshooting (quick checks)
|
||||
|
||||
If auto-enrichment appears to do nothing:
|
||||
|
||||
1. Confirm proxy listener is running while SubMiner is active:
|
||||
|
||||
```bash
|
||||
ss -ltnp | rg 8766
|
||||
```
|
||||
|
||||
2. Confirm requests can pass through the proxy:
|
||||
|
||||
```bash
|
||||
curl -sS http://127.0.0.1:8766 \
|
||||
-H 'content-type: application/json' \
|
||||
-d '{"action":"version","version":2}'
|
||||
```
|
||||
|
||||
3. Check both log sinks:
|
||||
|
||||
- Launcher/mpv-integrated log: `~/.cache/SubMiner/mp.log`
|
||||
- App runtime log: `~/.config/SubMiner/logs/SubMiner-YYYY-MM-DD.log`
|
||||
|
||||
4. Ensure config JSONC is valid and logging shape is correct:
|
||||
|
||||
```jsonc
|
||||
"logging": {
|
||||
"level": "debug"
|
||||
}
|
||||
```
|
||||
|
||||
`"logging": "debug"` is invalid for current schema and can break reload/start behavior.
|
||||
|
||||
## Field Mapping
|
||||
|
||||
SubMiner maps its data to your Anki note fields. Configure these under `ankiConnect.fields`:
|
||||
|
||||
```jsonc
|
||||
"ankiConnect": {
|
||||
"fields": {
|
||||
"audio": "ExpressionAudio", // audio clip from the video
|
||||
"image": "Picture", // screenshot or animated clip
|
||||
"sentence": "Sentence", // subtitle text
|
||||
"miscInfo": "MiscInfo", // metadata (filename, timestamp)
|
||||
"translation": "SelectionText" // secondary sub or AI translation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Field names must match your Anki note type exactly (case-sensitive). If a configured field does not exist on the note type, SubMiner skips it without error.
|
||||
|
||||
### Minimal Config
|
||||
|
||||
If you only want sentence and audio on your cards:
|
||||
|
||||
```jsonc
|
||||
"ankiConnect": {
|
||||
"enabled": true,
|
||||
"fields": {
|
||||
"sentence": "Sentence",
|
||||
"audio": "ExpressionAudio"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Media Generation
|
||||
|
||||
SubMiner uses FFmpeg to generate audio and image media from the video. FFmpeg must be installed and on `PATH`.
|
||||
|
||||
### Audio
|
||||
|
||||
Audio is extracted from the video file using the subtitle's start and end timestamps, with configurable padding added before and after.
|
||||
|
||||
```jsonc
|
||||
"ankiConnect": {
|
||||
"media": {
|
||||
"generateAudio": true,
|
||||
"audioPadding": 0.5, // seconds before and after subtitle timing
|
||||
"maxMediaDuration": 30 // cap total duration in seconds
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Output format: MP3 at 44100 Hz. If the video has multiple audio streams, SubMiner uses the active stream.
|
||||
|
||||
The audio is uploaded to Anki's media folder and inserted as `[sound:audio_<timestamp>.mp3]`.
|
||||
|
||||
### Screenshots (Static)
|
||||
|
||||
A single frame is captured at the current playback position.
|
||||
|
||||
```jsonc
|
||||
"ankiConnect": {
|
||||
"media": {
|
||||
"generateImage": true,
|
||||
"imageType": "static",
|
||||
"imageFormat": "jpg", // "jpg", "png", or "webp"
|
||||
"imageQuality": 92, // 1–100
|
||||
"imageMaxWidth": null, // optional, preserves aspect ratio
|
||||
"imageMaxHeight": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Animated Clips (AVIF)
|
||||
|
||||
Instead of a static screenshot, SubMiner can generate an animated AVIF covering the subtitle duration.
|
||||
|
||||
```jsonc
|
||||
"ankiConnect": {
|
||||
"media": {
|
||||
"generateImage": true,
|
||||
"imageType": "avif",
|
||||
"animatedFps": 10,
|
||||
"animatedMaxWidth": 640,
|
||||
"animatedMaxHeight": null,
|
||||
"animatedCrf": 35 // 0–63, lower = better quality
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Animated AVIF requires an AV1 encoder (`libaom-av1`, `libsvtav1`, or `librav1e`) in your FFmpeg build. Generation timeout is 60 seconds.
|
||||
|
||||
### Behavior Options
|
||||
|
||||
```jsonc
|
||||
"ankiConnect": {
|
||||
"behavior": {
|
||||
"overwriteAudio": true, // replace existing audio, or append
|
||||
"overwriteImage": true, // replace existing image, or append
|
||||
"mediaInsertMode": "append", // "append" or "prepend" to field content
|
||||
"autoUpdateNewCards": true, // auto-update when new card detected
|
||||
"notificationType": "osd" // "osd", "system", "both", or "none"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## AI Translation
|
||||
|
||||
SubMiner can auto-translate the mined sentence and fill the translation field. By default, if a secondary subtitle track is available, its text is used. When AI is enabled, SubMiner calls an LLM API instead.
|
||||
|
||||
```jsonc
|
||||
"ankiConnect": {
|
||||
"ai": {
|
||||
"enabled": true,
|
||||
"alwaysUseAiTranslation": false, // true = ignore secondary sub
|
||||
"apiKey": "sk-...",
|
||||
"model": "openai/gpt-4o-mini",
|
||||
"baseUrl": "https://openrouter.ai/api",
|
||||
"targetLanguage": "English",
|
||||
"systemPrompt": "You are a translation engine. Return only the translation."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Translation priority:
|
||||
|
||||
1. If `alwaysUseAiTranslation` is `true`, always call the AI API.
|
||||
2. If a secondary subtitle is available, use it as the translation.
|
||||
3. If AI is enabled and no secondary subtitle exists, call the AI API.
|
||||
4. Otherwise, leave the field empty.
|
||||
|
||||
## Sentence Cards (Lapis)
|
||||
|
||||
SubMiner can create standalone sentence cards (without a word/expression) using a separate note type. This is designed for use with [Lapis](https://github.com/donkuri/Lapis) and similar sentence-focused note types.
|
||||
|
||||
```jsonc
|
||||
"ankiConnect": {
|
||||
"isLapis": {
|
||||
"enabled": true,
|
||||
"sentenceCardModel": "Japanese sentences"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Trigger with the mine sentence shortcut (`Ctrl/Cmd+S` by default). The card is created directly via AnkiConnect with the sentence, audio, and image filled in.
|
||||
|
||||
To mine multiple subtitle lines as one sentence card, use `Ctrl/Cmd+Shift+S` followed by a digit (1–9) to select how many recent lines to combine.
|
||||
|
||||
## Field Grouping (Kiku)
|
||||
|
||||
When you mine the same word multiple times, SubMiner can merge the cards instead of creating duplicates. This is designed for note types like [Kiku](https://github.com/youyoumu/kiku) that support grouped sentence/audio/image fields.
|
||||
|
||||
```jsonc
|
||||
"ankiConnect": {
|
||||
"isKiku": {
|
||||
"enabled": true,
|
||||
"fieldGrouping": "manual", // "auto", "manual", or "disabled"
|
||||
"deleteDuplicateInAuto": true // delete new card after auto-merge
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Modes
|
||||
|
||||
**Disabled** (`"disabled"`): No duplicate detection. Each card is independent.
|
||||
|
||||
**Auto** (`"auto"`): When a duplicate expression is found, SubMiner merges the new card into the existing one automatically. Both sentences, audio clips, and images are preserved, and exact duplicate values are collapsed to one entry. If `deleteDuplicateInAuto` is true, the new card is deleted after merging.
|
||||
|
||||
**Manual** (`"manual"`): A modal appears in the overlay showing both cards. You choose which card to keep, preview the merge result, then confirm. The modal has a 90-second timeout, after which it cancels automatically.
|
||||
|
||||
### What Gets Merged
|
||||
|
||||
| Field | Merge behavior |
|
||||
| -------- | --------------------------------------------------------------- |
|
||||
| Sentence | Both sentences preserved (exact duplicate text is deduplicated) |
|
||||
| Audio | Both `[sound:...]` entries kept (exact duplicates deduplicated) |
|
||||
| Image | Both images kept (exact duplicates deduplicated) |
|
||||
|
||||
### Keyboard Shortcuts in the Modal
|
||||
|
||||
| Key | Action |
|
||||
| --------- | ---------------------------------- |
|
||||
| `1` / `2` | Select card 1 or card 2 to keep |
|
||||
| `Enter` | Confirm selection |
|
||||
| `Esc` | Cancel (keep both cards unchanged) |
|
||||
|
||||
## Full Config Example
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"ankiConnect": {
|
||||
"enabled": true,
|
||||
"url": "http://127.0.0.1:8765",
|
||||
"pollingRate": 3000,
|
||||
"proxy": {
|
||||
"enabled": false,
|
||||
"host": "127.0.0.1",
|
||||
"port": 8766,
|
||||
"upstreamUrl": "http://127.0.0.1:8765",
|
||||
},
|
||||
"fields": {
|
||||
"audio": "ExpressionAudio",
|
||||
"image": "Picture",
|
||||
"sentence": "Sentence",
|
||||
"miscInfo": "MiscInfo",
|
||||
"translation": "SelectionText",
|
||||
},
|
||||
"media": {
|
||||
"generateAudio": true,
|
||||
"generateImage": true,
|
||||
"imageType": "static",
|
||||
"imageFormat": "jpg",
|
||||
"imageQuality": 92,
|
||||
"audioPadding": 0.5,
|
||||
"maxMediaDuration": 30,
|
||||
},
|
||||
"behavior": {
|
||||
"overwriteAudio": true,
|
||||
"overwriteImage": true,
|
||||
"mediaInsertMode": "append",
|
||||
"autoUpdateNewCards": true,
|
||||
"notificationType": "osd",
|
||||
},
|
||||
"ai": {
|
||||
"enabled": false,
|
||||
"apiKey": "",
|
||||
"model": "openai/gpt-4o-mini",
|
||||
"baseUrl": "https://openrouter.ai/api",
|
||||
"targetLanguage": "English",
|
||||
},
|
||||
"isKiku": {
|
||||
"enabled": false,
|
||||
"fieldGrouping": "disabled",
|
||||
"deleteDuplicateInAuto": true,
|
||||
},
|
||||
"isLapis": {
|
||||
"enabled": false,
|
||||
"sentenceCardModel": "Japanese sentences",
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user