Compare commits
160 Commits
v0.3.0
..
e84674e3b5
| Author | SHA1 | Date | |
|---|---|---|---|
| e84674e3b5 | |||
|
6ca5cede3e
|
|||
|
4d010e6a18
|
|||
| 5250ca8214 | |||
| 49f89e6452 | |||
|
89723e2ccb
|
|||
|
d05e2bd8ec
|
|||
|
7484d3c102
|
|||
|
f78a875ba3
|
|||
|
a025652542
|
|||
| 91a01b86a9 | |||
| 105713361e | |||
| 4cb0dbfaad | |||
|
801cdcafca
|
|||
|
094bcce0dc
|
|||
|
d1ec678d7a
|
|||
|
f0324cd93a
|
|||
|
1b2ee03678
|
|||
|
18e61d2048
|
|||
|
f0a11c2c99
|
|||
|
bc8d1edf6f
|
|||
| d48d880ba3 | |||
| 7c9b65db8b | |||
| e5c1135501 | |||
| 430373f010 | |||
|
b68d17614d
|
|||
| 30712738dc | |||
| 0915b23dc8 | |||
| db30c61327 | |||
|
27f5b2bb58
|
|||
|
baabdb6d30
|
|||
|
3a67e23bc3
|
|||
|
13e2b5f8c8
|
|||
| 53aa58d044 | |||
| d8934647a9 | |||
|
7ac51cd5e9
|
|||
| 52bab1d611 | |||
|
49e46e6b9b
|
|||
|
c1c40c8d40
|
|||
|
c71482cb44
|
|||
| 05cf4a6fe5 | |||
|
9b4de93283
|
|||
|
16ffbbc4b3
|
|||
|
de4f3efa30
|
|||
| bc7dde3b02 | |||
|
7a64488ed5
|
|||
| 5f3c3871d3 | |||
| 4d24e22bb5 | |||
| c47cfb52af | |||
|
da0087bba6
|
|||
|
8338f27794
|
|||
|
b029d65c90
|
|||
|
c24f99899b
|
|||
|
3aca581764
|
|||
|
ba540d09b2
|
|||
|
6530d2ccbc
|
|||
|
a784091ecb
|
|||
| 61c3e1e3c6 | |||
|
ce76a75630
|
|||
|
52249db5b4
|
|||
|
09d8b52fbf
|
|||
|
0edd566904
|
|||
|
6eb1b0f197
|
|||
|
e4137d9760
|
|||
|
864f4124ae
|
|||
| 7514985feb | |||
| d6c72806bb | |||
|
3502cdc607
|
|||
| d51e7fe401 | |||
|
f9a4039ad2
|
|||
|
8e5c21b443
|
|||
|
55b350c3a2
|
|||
|
54324df3be
|
|||
| 35adf8299c | |||
|
2d4f2d1139
|
|||
|
77e632276b
|
|||
|
4c95b57885
|
|||
|
242402b253
|
|||
|
61d15f9431
|
|||
|
508864bcbb
|
|||
|
23c54bb01e
|
|||
|
ec667c64e8
|
|||
|
39b2ccad8e
|
|||
|
23815945bf
|
|||
|
9dca83acd9
|
|||
|
55300e2d8c
|
|||
|
28afd15134
|
|||
|
58304757aa
|
|||
|
c95518e94a
|
|||
| 5ee4617607 | |||
|
842008b089
|
|||
|
6f56a0bcf6
|
|||
| 5feed360ca | |||
| c17f0a4080 | |||
| 0317c7f011 | |||
|
13797b5005
|
|||
|
b24d9d7487
|
|||
| 3a01cffc6b | |||
|
eddf6f0456
|
|||
|
f6c024d61e
|
|||
| 6749ff843c | |||
|
42abdd1268
|
|||
|
5d914b1547
|
|||
| 50b45cac0b | |||
|
e35aac6ee0
|
|||
|
fe2da22d29
|
|||
|
2045ffbff8
|
|||
| 478869ff28 | |||
| 9eed37420e | |||
| 99f4d2baaf | |||
|
f4e8c3feec
|
|||
|
d0b308f340
|
|||
| 1b56360a24 | |||
|
68833c76c4
|
|||
| 4d7c80f2e4 | |||
|
2f17859b7b
|
|||
|
9c7e02cbf0
|
|||
|
5f320edab5
|
|||
|
f7ce3371a1
|
|||
|
71805dccd7
|
|||
|
a13b7352d7
|
|||
|
ff5ecdaded
|
|||
|
b32c3cf58c
|
|||
|
b791262860
|
|||
|
29b8c6e02a
|
|||
|
618727d8e8
|
|||
|
fed60c265d
|
|||
|
a34a7489db
|
|||
|
e59192bbe1
|
|||
|
e0f82d28f0
|
|||
|
a0521aeeaf
|
|||
|
2127f759ca
|
|||
|
5e787183d0
|
|||
|
81ca31b899
|
|||
|
e2a7597b4f
|
|||
|
2e59c21078
|
|||
|
7b5ab3294d
|
|||
|
2bbf38f987
|
|||
|
f09c91494d
|
|||
|
58ec9b76e0
|
|||
|
7a196f69d6
|
|||
| c799a8de3c | |||
|
34d2dce8dc
|
|||
|
3a22a97761
|
|||
|
962243e959
|
|||
|
021010a338
|
|||
|
4c0575afe0
|
|||
|
9e46176519
|
|||
|
f10e905dbd
|
|||
| e4aa8ff907 | |||
|
a6ece5388a
|
|||
|
6a44b54b51
|
|||
|
93cd688625
|
|||
|
8e319a417d
|
|||
|
38034db1e4
|
|||
|
f775f90360
|
|||
|
55dff6ced7
|
|||
|
d0c11d347b
|
|||
|
f0418c6e56
|
|||
| e18985fb14 |
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "subminer-local",
|
||||
"interface": {
|
||||
"displayName": "SubMiner Local"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "subminer-workflow",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/subminer-workflow"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "Productivity"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
---
|
||||
name: 'subminer-change-verification'
|
||||
description: 'Compatibility shim. Canonical SubMiner change verification workflow now lives in the repo-local subminer-workflow plugin.'
|
||||
---
|
||||
|
||||
# Compatibility Shim
|
||||
|
||||
Canonical source:
|
||||
|
||||
- `plugins/subminer-workflow/skills/subminer-change-verification/SKILL.md`
|
||||
|
||||
Canonical helper scripts:
|
||||
|
||||
- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh`
|
||||
- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh`
|
||||
|
||||
When this shim is invoked:
|
||||
|
||||
1. Read the canonical plugin-owned skill.
|
||||
2. Follow the plugin-owned skill as the source of truth.
|
||||
3. Use the wrapper scripts in this shim directory only for compatibility with existing commands and docs.
|
||||
4. Do not duplicate workflow changes here; update the plugin-owned skill and scripts instead.
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
REPO_ROOT=$(cd "$SCRIPT_DIR/../../../.." && pwd)
|
||||
TARGET="$REPO_ROOT/plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh"
|
||||
|
||||
if [[ ! -x "$TARGET" ]]; then
|
||||
echo "Missing canonical script: $TARGET" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec "$TARGET" "$@"
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
REPO_ROOT=$(cd "$SCRIPT_DIR/../../../.." && pwd)
|
||||
TARGET="$REPO_ROOT/plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh"
|
||||
|
||||
if [[ ! -x "$TARGET" ]]; then
|
||||
echo "Missing canonical script: $TARGET" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec "$TARGET" "$@"
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
name: 'subminer-scrum-master'
|
||||
description: 'Compatibility shim. Canonical SubMiner scrum-master workflow now lives in the repo-local subminer-workflow plugin.'
|
||||
---
|
||||
|
||||
# Compatibility Shim
|
||||
|
||||
Canonical source:
|
||||
|
||||
- `plugins/subminer-workflow/skills/subminer-scrum-master/SKILL.md`
|
||||
|
||||
When this shim is invoked:
|
||||
|
||||
1. Read the canonical plugin-owned skill.
|
||||
2. Follow the plugin-owned skill as the source of truth.
|
||||
3. Do not duplicate workflow changes here; update the plugin-owned skill instead.
|
||||
|
||||
This shim exists so existing repo references and prompts keep resolving during the migration to the repo-local plugin workflow.
|
||||
@@ -0,0 +1,3 @@
|
||||
## Checklist
|
||||
|
||||
- [ ] Added a changelog fragment in `changes/`, or this PR is labeled `skip-changelog`
|
||||
@@ -13,6 +13,7 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
|
||||
- name: Setup Bun
|
||||
@@ -26,20 +27,50 @@ jobs:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock') }}
|
||||
stats/node_modules
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Lint changelog fragments
|
||||
run: bun run changelog:lint
|
||||
|
||||
- name: Lint stats (formatting)
|
||||
run: bun run lint:stats
|
||||
|
||||
- name: Enforce pull request changelog fragments (`skip-changelog` label bypass)
|
||||
if: github.event_name == 'pull_request'
|
||||
run: bun run changelog:pr-check --base-ref "origin/${{ github.base_ref }}" --head-ref "HEAD" --labels "${{ join(github.event.pull_request.labels.*.name, ',') }}"
|
||||
|
||||
- name: Build (TypeScript check)
|
||||
# Keep explicit typecheck for fast fail before full build/bundle.
|
||||
run: bun run tsc --noEmit
|
||||
run: bun run typecheck
|
||||
|
||||
- name: Verify generated config examples
|
||||
run: bun run verify:config-example
|
||||
|
||||
- name: Internal docs knowledge-base checks
|
||||
run: bun run test:docs:kb
|
||||
|
||||
- name: Test suite (source)
|
||||
run: bun run test:fast
|
||||
|
||||
- name: Coverage suite (maintained source lane)
|
||||
run: bun run test:coverage:src
|
||||
|
||||
- name: Upload coverage artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-test-src
|
||||
path: coverage/test-src/lcov.info
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Launcher smoke suite (source)
|
||||
run: bun run test:launcher:smoke:src
|
||||
|
||||
@@ -54,12 +85,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
|
||||
|
||||
@@ -0,0 +1,426 @@
|
||||
name: Prerelease
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*-beta.*'
|
||||
- 'v*-rc.*'
|
||||
|
||||
concurrency:
|
||||
group: prerelease-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
quality-gate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
stats/node_modules
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.arch }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Lint stats (formatting)
|
||||
run: bun run lint:stats
|
||||
|
||||
- name: Build (TypeScript check)
|
||||
run: bun run typecheck
|
||||
|
||||
- name: Install Lua
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y lua5.4
|
||||
sudo ln -sf /usr/bin/lua5.4 /usr/local/bin/lua
|
||||
lua -v
|
||||
|
||||
- name: Test suite (source)
|
||||
run: bun run test:fast
|
||||
|
||||
- name: Environment suite
|
||||
run: bun run test:env
|
||||
|
||||
- name: Coverage suite (maintained source lane)
|
||||
run: bun run test:coverage:src
|
||||
|
||||
- name: Upload coverage artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-test-src
|
||||
path: coverage/test-src/lcov.info
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Launcher smoke suite (source)
|
||||
run: bun run test:launcher:smoke:src
|
||||
|
||||
- name: Upload launcher smoke artifacts (on failure)
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: launcher-smoke
|
||||
path: .tmp/launcher-smoke/**
|
||||
if-no-files-found: ignore
|
||||
|
||||
- name: Build (bundle)
|
||||
run: bun run build
|
||||
|
||||
- name: Immersion SQLite verification
|
||||
run: bun run test:immersion:sqlite:dist
|
||||
|
||||
- name: Dist smoke suite
|
||||
run: bun run test:smoke:dist
|
||||
|
||||
build-linux:
|
||||
needs: [quality-gate]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
stats/node_modules
|
||||
vendor/texthooker-ui/node_modules
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.arch }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Build texthooker-ui
|
||||
run: |
|
||||
cd vendor/texthooker-ui
|
||||
bun install
|
||||
bun run build
|
||||
|
||||
- name: Build AppImage
|
||||
run: bun run build:appimage
|
||||
|
||||
- name: Build unversioned AppImage
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
appimages=(release/SubMiner-*.AppImage)
|
||||
if [ "${#appimages[@]}" -eq 0 ]; then
|
||||
echo "No versioned AppImage found to create unversioned artifact."
|
||||
ls -la release
|
||||
exit 1
|
||||
fi
|
||||
cp "${appimages[0]}" release/SubMiner.AppImage
|
||||
|
||||
- name: Upload AppImage artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: appimage
|
||||
path: |
|
||||
release/*.AppImage
|
||||
release/*.yml
|
||||
release/*.blockmap
|
||||
if-no-files-found: error
|
||||
|
||||
build-macos:
|
||||
needs: [quality-gate]
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
stats/node_modules
|
||||
vendor/texthooker-ui/node_modules
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.arch }}-bun-
|
||||
|
||||
- name: Validate macOS signing/notarization secrets
|
||||
run: |
|
||||
missing=0
|
||||
for name in CSC_LINK CSC_KEY_PASSWORD APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID; do
|
||||
if [ -z "${!name}" ]; then
|
||||
echo "Missing required secret: $name"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
if [ "$missing" -ne 0 ]; then
|
||||
echo "Set all required macOS signing/notarization secrets and rerun."
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Build texthooker-ui
|
||||
run: |
|
||||
cd vendor/texthooker-ui
|
||||
bun install
|
||||
bun run build
|
||||
|
||||
- name: Build signed + notarized macOS artifacts
|
||||
run: bun run build:mac
|
||||
env:
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Upload macOS artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos
|
||||
path: |
|
||||
release/*.dmg
|
||||
release/*.zip
|
||||
release/*.yml
|
||||
release/*.blockmap
|
||||
if-no-files-found: error
|
||||
|
||||
build-windows:
|
||||
needs: [quality-gate]
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
stats/node_modules
|
||||
vendor/texthooker-ui/node_modules
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.arch }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Build texthooker-ui
|
||||
shell: powershell
|
||||
run: |
|
||||
Set-Location vendor/texthooker-ui
|
||||
bun install
|
||||
bun run build
|
||||
|
||||
- name: Build unsigned Windows artifacts
|
||||
run: bun run build:win:unsigned
|
||||
|
||||
- name: Upload Windows artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows
|
||||
path: |
|
||||
release/*.exe
|
||||
release/*.zip
|
||||
release/*.yml
|
||||
release/*.blockmap
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
needs: [build-linux, build-macos, build-windows]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Download AppImage
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: appimage
|
||||
path: release
|
||||
|
||||
- name: Download macOS artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: macos
|
||||
path: release
|
||||
|
||||
- name: Download Windows artifacts
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: windows
|
||||
path: release
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
key: ${{ runner.os }}-${{ runner.arch }}-bun-${{ hashFiles('bun.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ runner.arch }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Build Bun subminer wrapper
|
||||
run: make build-launcher
|
||||
|
||||
- name: Verify Bun subminer wrapper
|
||||
run: dist/launcher/subminer --help >/dev/null
|
||||
|
||||
- name: Enforce generated launcher workflow
|
||||
run: bash scripts/verify-generated-launcher.sh
|
||||
|
||||
- name: Verify generated config examples
|
||||
run: bun run verify:config-example
|
||||
|
||||
- name: Package optional assets bundle
|
||||
run: |
|
||||
tar -czf "release/subminer-assets.tar.gz" \
|
||||
config.example.jsonc \
|
||||
plugin/subminer \
|
||||
plugin/subminer.conf \
|
||||
assets/themes/subminer.rasi
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
files=(release/*.AppImage release/*.dmg release/*.exe release/*.zip release/*.tar.gz release/*.yml release/*.blockmap dist/launcher/subminer)
|
||||
if [ "${#files[@]}" -eq 0 ]; then
|
||||
echo "No release artifacts found for checksum generation."
|
||||
exit 1
|
||||
fi
|
||||
: > release/SHA256SUMS.txt
|
||||
for file in "${files[@]}"; do
|
||||
printf '%s %s\n' \
|
||||
"$(sha256sum "$file" | awk '{print $1}')" \
|
||||
"${file##*/}" >> release/SHA256SUMS.txt
|
||||
done
|
||||
|
||||
- name: Get version from tag
|
||||
id: version
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Verify committed prerelease notes
|
||||
run: |
|
||||
if [ ! -s release/prerelease-notes.md ]; then
|
||||
echo "::error::release/prerelease-notes.md is missing or empty. Run 'bun run changelog:prerelease-notes --version <version>' locally and commit the file before tagging."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Publish Prerelease
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
shopt -s nullglob
|
||||
artifacts=(
|
||||
release/*.AppImage
|
||||
release/*.dmg
|
||||
release/*.exe
|
||||
release/*.zip
|
||||
release/*.tar.gz
|
||||
release/*.yml
|
||||
release/*.blockmap
|
||||
release/SHA256SUMS.txt
|
||||
dist/launcher/subminer
|
||||
)
|
||||
|
||||
if [ "${#artifacts[@]}" -eq 0 ]; then
|
||||
echo "No release artifacts found for upload."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if gh release view "${{ steps.version.outputs.VERSION }}" >/dev/null 2>&1; then
|
||||
gh release edit "${{ steps.version.outputs.VERSION }}" \
|
||||
--draft \
|
||||
--prerelease \
|
||||
--title "${{ steps.version.outputs.VERSION }}" \
|
||||
--notes-file release/prerelease-notes.md
|
||||
else
|
||||
gh release create "${{ steps.version.outputs.VERSION }}" \
|
||||
--draft \
|
||||
--latest=false \
|
||||
--prerelease \
|
||||
--title "${{ steps.version.outputs.VERSION }}" \
|
||||
--notes-file release/prerelease-notes.md
|
||||
fi
|
||||
|
||||
for asset in "${artifacts[@]}"; do
|
||||
gh release upload "${{ steps.version.outputs.VERSION }}" "$asset" --clobber
|
||||
done
|
||||
|
||||
gh release edit "${{ steps.version.outputs.VERSION }}" \
|
||||
--draft=false \
|
||||
--prerelease \
|
||||
--title "${{ steps.version.outputs.VERSION }}" \
|
||||
--notes-file release/prerelease-notes.md
|
||||
@@ -4,14 +4,13 @@ on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
- '!v*-beta.*'
|
||||
- '!v*-rc.*'
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
quality-gate:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -26,25 +25,42 @@ 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') }}
|
||||
stats/node_modules
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Lint stats (formatting)
|
||||
run: bun run lint:stats
|
||||
|
||||
- name: Build (TypeScript check)
|
||||
run: bun run typecheck
|
||||
|
||||
- name: Test suite (source)
|
||||
run: bun run test:fast
|
||||
|
||||
- name: Coverage suite (maintained source lane)
|
||||
run: bun run test:coverage:src
|
||||
|
||||
- name: Upload coverage artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: coverage-test-src
|
||||
path: coverage/test-src/lcov.info
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Launcher smoke suite (source)
|
||||
run: bun run test:launcher:smoke:src
|
||||
|
||||
@@ -59,6 +75,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
|
||||
|
||||
@@ -82,13 +101,17 @@ jobs:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
stats/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', 'stats/bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Build texthooker-ui
|
||||
run: |
|
||||
@@ -98,8 +121,6 @@ jobs:
|
||||
|
||||
- name: Build AppImage
|
||||
run: bun run build:appimage
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build unversioned AppImage
|
||||
run: |
|
||||
@@ -116,7 +137,10 @@ jobs:
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: appimage
|
||||
path: release/*.AppImage
|
||||
path: |
|
||||
release/*.AppImage
|
||||
release/*.yml
|
||||
release/*.blockmap
|
||||
|
||||
build-macos:
|
||||
needs: [quality-gate]
|
||||
@@ -138,8 +162,10 @@ jobs:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
stats/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', 'stats/bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
@@ -164,7 +190,9 @@ jobs:
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Build texthooker-ui
|
||||
run: |
|
||||
@@ -175,7 +203,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 }}
|
||||
@@ -189,10 +216,67 @@ jobs:
|
||||
path: |
|
||||
release/*.dmg
|
||||
release/*.zip
|
||||
release/*.yml
|
||||
release/*.blockmap
|
||||
|
||||
build-windows:
|
||||
needs: [quality-gate]
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
stats/node_modules
|
||||
vendor/texthooker-ui/node_modules
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Build texthooker-ui
|
||||
shell: powershell
|
||||
run: |
|
||||
Set-Location vendor/texthooker-ui
|
||||
bun install
|
||||
bun run build
|
||||
|
||||
- name: Build unsigned Windows artifacts
|
||||
run: bun run build:win:unsigned
|
||||
|
||||
- name: Upload Windows artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows
|
||||
path: |
|
||||
release/*.exe
|
||||
release/*.zip
|
||||
release/*.yml
|
||||
release/*.blockmap
|
||||
if-no-files-found: error
|
||||
|
||||
release:
|
||||
needs: [build-linux, build-macos]
|
||||
needs: [build-linux, build-macos, build-windows]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -211,6 +295,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:
|
||||
@@ -238,6 +328,9 @@ jobs:
|
||||
- name: Enforce generated launcher workflow
|
||||
run: bash scripts/verify-generated-launcher.sh
|
||||
|
||||
- name: Verify generated config examples
|
||||
run: bun run verify:config-example
|
||||
|
||||
- name: Package optional assets bundle
|
||||
run: |
|
||||
tar -czf "release/subminer-assets.tar.gz" \
|
||||
@@ -249,34 +342,34 @@ 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 release/*.yml release/*.blockmap dist/launcher/subminer)
|
||||
if [ "${#files[@]}" -eq 0 ]; then
|
||||
echo "No release artifacts found for checksum generation."
|
||||
exit 1
|
||||
fi
|
||||
sha256sum "${files[@]}" > release/SHA256SUMS.txt
|
||||
: > release/SHA256SUMS.txt
|
||||
for file in "${files[@]}"; do
|
||||
printf '%s %s\n' \
|
||||
"$(sha256sum "$file" | awk '{print $1}')" \
|
||||
"${file##*/}" >> release/SHA256SUMS.txt
|
||||
done
|
||||
|
||||
- name: Get version from tag
|
||||
id: version
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Generate changelog
|
||||
id: changelog
|
||||
- name: Guard against pending changelog fragments
|
||||
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)
|
||||
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
|
||||
if find changes -maxdepth 1 -name '*.md' -not -name README.md -print -quit | grep -q .; then
|
||||
echo "::error::Pending changelog fragments detected. Run 'bun run changelog:build --version ${{ steps.version.outputs.VERSION }}' locally and commit the polished CHANGELOG.md before tagging. CI no longer auto-builds the changelog because the polish step requires the local 'claude' CLI."
|
||||
exit 1
|
||||
fi
|
||||
echo "CHANGES<<EOF" >> $GITHUB_OUTPUT
|
||||
echo "$CHANGES" >> $GITHUB_OUTPUT
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- 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:
|
||||
@@ -284,55 +377,27 @@ jobs:
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
cat > release-body.md <<'EOF'
|
||||
## 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`.
|
||||
EOF
|
||||
|
||||
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-body.md \
|
||||
--prerelease false
|
||||
--notes-file release/release-notes.md
|
||||
else
|
||||
gh release create "${{ steps.version.outputs.VERSION }}" \
|
||||
--title "${{ steps.version.outputs.VERSION }}" \
|
||||
--notes-file release-body.md \
|
||||
--prerelease false
|
||||
--notes-file release/release-notes.md
|
||||
fi
|
||||
|
||||
shopt -s nullglob
|
||||
artifacts=(
|
||||
release/*.AppImage
|
||||
release/*.dmg
|
||||
release/*.exe
|
||||
release/*.zip
|
||||
release/*.tar.gz
|
||||
release/*.yml
|
||||
release/*.blockmap
|
||||
release/SHA256SUMS.txt
|
||||
dist/launcher/subminer
|
||||
)
|
||||
@@ -345,3 +410,130 @@ jobs:
|
||||
for asset in "${artifacts[@]}"; do
|
||||
gh release upload "${{ steps.version.outputs.VERSION }}" "$asset" --clobber
|
||||
done
|
||||
|
||||
aur-publish:
|
||||
needs: [release]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Get version from tag
|
||||
id: version
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Check AUR publish prerequisites
|
||||
id: aur_prereqs
|
||||
env:
|
||||
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [ -z "${AUR_SSH_PRIVATE_KEY}" ]; then
|
||||
echo "::warning::Missing AUR_SSH_PRIVATE_KEY; skipping automated AUR publish."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Configure SSH for AUR
|
||||
id: aur_ssh
|
||||
if: steps.aur_prereqs.outputs.skip != 'true'
|
||||
env:
|
||||
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if install -dm700 ~/.ssh \
|
||||
&& printf '%s\n' "${AUR_SSH_PRIVATE_KEY}" > ~/.ssh/aur \
|
||||
&& chmod 600 ~/.ssh/aur \
|
||||
&& ssh-keyscan aur.archlinux.org >> ~/.ssh/known_hosts \
|
||||
&& chmod 644 ~/.ssh/known_hosts; then
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "::warning::Unable to configure SSH for AUR; skipping automated AUR publish."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Clone AUR repo
|
||||
id: aur_clone
|
||||
if: steps.aur_prereqs.outputs.skip != 'true' && steps.aur_ssh.outputs.skip != 'true'
|
||||
env:
|
||||
GIT_SSH_COMMAND: ssh -i ~/.ssh/aur -o IdentitiesOnly=yes
|
||||
run: |
|
||||
set -euo pipefail
|
||||
attempts=3
|
||||
for attempt in $(seq 1 "$attempts"); do
|
||||
if git clone ssh://aur@aur.archlinux.org/subminer-bin.git aur-subminer-bin; then
|
||||
echo "skip=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
rm -rf aur-subminer-bin
|
||||
|
||||
if [ "$attempt" -lt "$attempts" ]; then
|
||||
sleep $((attempt * 15))
|
||||
fi
|
||||
done
|
||||
|
||||
echo "::warning::Unable to clone subminer-bin from AUR after ${attempts} attempts; skipping automated AUR publish."
|
||||
echo "skip=true" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Download release assets for AUR
|
||||
if: steps.aur_prereqs.outputs.skip != 'true' && steps.aur_ssh.outputs.skip != 'true' && steps.aur_clone.outputs.skip != 'true'
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="${{ steps.version.outputs.VERSION }}"
|
||||
install -dm755 .tmp/aur-release-assets
|
||||
gh release download "$version" \
|
||||
--dir .tmp/aur-release-assets \
|
||||
--pattern "SubMiner-${version#v}.AppImage" \
|
||||
--pattern "subminer" \
|
||||
--pattern "subminer-assets.tar.gz"
|
||||
|
||||
- name: Update AUR packaging metadata
|
||||
if: steps.aur_prereqs.outputs.skip != 'true' && steps.aur_ssh.outputs.skip != 'true' && steps.aur_clone.outputs.skip != 'true'
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version_no_v="${{ steps.version.outputs.VERSION }}"
|
||||
version_no_v="${version_no_v#v}"
|
||||
cp packaging/aur/subminer-bin/PKGBUILD aur-subminer-bin/PKGBUILD
|
||||
cp packaging/aur/subminer-bin/.SRCINFO aur-subminer-bin/.SRCINFO
|
||||
bash scripts/update-aur-package.sh \
|
||||
--pkg-dir aur-subminer-bin \
|
||||
--version "${{ steps.version.outputs.VERSION }}" \
|
||||
--appimage ".tmp/aur-release-assets/SubMiner-${version_no_v}.AppImage" \
|
||||
--wrapper ".tmp/aur-release-assets/subminer" \
|
||||
--assets ".tmp/aur-release-assets/subminer-assets.tar.gz"
|
||||
|
||||
- name: Commit and push AUR update
|
||||
if: steps.aur_prereqs.outputs.skip != 'true' && steps.aur_ssh.outputs.skip != 'true' && steps.aur_clone.outputs.skip != 'true'
|
||||
working-directory: aur-subminer-bin
|
||||
env:
|
||||
GIT_SSH_COMMAND: ssh -i ~/.ssh/aur -o IdentitiesOnly=yes
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if git diff --quiet -- PKGBUILD .SRCINFO; then
|
||||
echo "AUR packaging already up to date."
|
||||
exit 0
|
||||
fi
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
git add PKGBUILD .SRCINFO
|
||||
git commit -m "Update to ${{ steps.version.outputs.VERSION }}"
|
||||
|
||||
attempts=3
|
||||
for attempt in $(seq 1 "$attempts"); do
|
||||
if git push origin HEAD:master; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [ "$attempt" -lt "$attempts" ]; then
|
||||
sleep $((attempt * 15))
|
||||
fi
|
||||
done
|
||||
|
||||
echo "::warning::Unable to push the AUR update after ${attempts} attempts; GitHub release is published, but subminer-bin needs manual follow-up."
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Superpowers brainstorming
|
||||
.superpowers/
|
||||
|
||||
# Electron build output
|
||||
out/
|
||||
dist/
|
||||
release/
|
||||
release/*
|
||||
!release/
|
||||
!release/release-notes.md
|
||||
!release/prerelease-notes.md
|
||||
build/yomitan/
|
||||
coverage/
|
||||
|
||||
# Launcher build artifact (produced by make build-launcher)
|
||||
/subminer
|
||||
@@ -21,9 +29,7 @@ Thumbs.db
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
**/CLAUDE.md
|
||||
environment.toml
|
||||
**/CLAUDE.md
|
||||
.env
|
||||
.vscode/*
|
||||
|
||||
@@ -34,5 +40,21 @@ docs/.vitepress/cache/
|
||||
docs/.vitepress/dist/
|
||||
tests/*
|
||||
.worktrees/
|
||||
.tmp/
|
||||
.codex/*
|
||||
.agents/*
|
||||
!.agents/skills/
|
||||
.agents/skills/*
|
||||
!.agents/skills/subminer-change-verification/
|
||||
!.agents/skills/subminer-scrum-master/
|
||||
.agents/skills/subminer-change-verification/*
|
||||
!.agents/skills/subminer-change-verification/SKILL.md
|
||||
!.agents/skills/subminer-change-verification/scripts/
|
||||
.agents/skills/subminer-change-verification/scripts/*
|
||||
!.agents/skills/subminer-change-verification/scripts/classify_subminer_diff.sh
|
||||
!.agents/skills/subminer-change-verification/scripts/verify_subminer_change.sh
|
||||
.agents/skills/subminer-scrum-master/*
|
||||
!.agents/skills/subminer-scrum-master/SKILL.md
|
||||
favicon.png
|
||||
.claude/*
|
||||
!stats/public/favicon.png
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,29 +1,66 @@
|
||||
<!-- BACKLOG.MD MCP GUIDELINES START -->
|
||||
# AGENTS.MD
|
||||
|
||||
<CRITICAL_INSTRUCTION>
|
||||
## Internal Docs
|
||||
|
||||
## BACKLOG WORKFLOW INSTRUCTIONS
|
||||
Start here, then leave this file.
|
||||
|
||||
This project uses Backlog.md MCP for all task and project management activities.
|
||||
- Internal system of record: [`docs/README.md`](./docs/README.md)
|
||||
- Architecture map: [`docs/architecture/README.md`](./docs/architecture/README.md)
|
||||
- Workflow map: [`docs/workflow/README.md`](./docs/workflow/README.md)
|
||||
- Verification lanes: [`docs/workflow/verification.md`](./docs/workflow/verification.md)
|
||||
- Knowledge-base rules: [`docs/knowledge-base/README.md`](./docs/knowledge-base/README.md)
|
||||
- Release guide: [`docs/RELEASING.md`](./docs/RELEASING.md)
|
||||
|
||||
**CRITICAL GUIDANCE**
|
||||
`docs-site/` is user-facing. Do not treat it as the canonical internal source of truth.
|
||||
|
||||
- If your client supports MCP resources, read `backlog://workflow/overview` to understand when and how to use Backlog for this project.
|
||||
- If your client only supports tools or the above request fails, call `backlog.get_workflow_overview()` tool to load the tool-oriented overview (it lists the matching guide tools).
|
||||
## Quick Start
|
||||
|
||||
- **First time working here?** Read the overview resource IMMEDIATELY to learn the workflow
|
||||
- **Already familiar?** You should have the overview cached ("## Backlog.md Overview (MCP)")
|
||||
- **When to read it**: BEFORE creating tasks, or when you're unsure whether to track work
|
||||
- Init workspace: `git submodule update --init --recursive`
|
||||
- Install deps: `make deps` or `bun install` plus `(cd vendor/texthooker-ui && bun install --frozen-lockfile)`
|
||||
- Fast dev loop: `make dev-watch`
|
||||
- Full local run: `bun run dev`
|
||||
- Verbose Electron debug: `electron . --start --dev --log-level debug`
|
||||
|
||||
These guides cover:
|
||||
## Build / Test
|
||||
|
||||
- Decision framework for when to create tasks
|
||||
- Search-first workflow to avoid duplicates
|
||||
- Links to detailed guides for task creation, execution, and finalization
|
||||
- MCP tools reference
|
||||
- Runtime/package manager: Bun (`packageManager: bun@1.3.5`)
|
||||
- Default handoff gate:
|
||||
`bun run typecheck`
|
||||
`bun run test:fast`
|
||||
`bun run test:env`
|
||||
`bun run build`
|
||||
`bun run test:smoke:dist`
|
||||
- If `docs-site/` changed, also run:
|
||||
`bun run docs:test`
|
||||
`bun run docs:build`
|
||||
- Prefer `make pretty` and `bun run format:check:src`
|
||||
|
||||
You MUST read the overview resource to understand the complete workflow. The information is NOT summarized here.
|
||||
## Change-Specific Checks
|
||||
|
||||
</CRITICAL_INSTRUCTION>
|
||||
- Config/schema/defaults: `bun run test:config`; if template/defaults changed, `bun run generate:config-example`
|
||||
- Launcher/plugin: `bun run test:launcher` or `bun run test:env`
|
||||
- Runtime-compat / dist-sensitive: `bun run test:runtime:compat`
|
||||
- Docs-only: `bun run docs:test`, then `bun run docs:build`
|
||||
|
||||
<!-- BACKLOG.MD MCP GUIDELINES END -->
|
||||
## Sensitive Files
|
||||
|
||||
- Launcher source of truth: `launcher/*.ts`
|
||||
- Generated launcher artifact: `dist/launcher/subminer`; never hand-edit it
|
||||
- Repo-root `./subminer` is stale; do not revive it
|
||||
- `bun run build` rebuilds bundled Yomitan from `vendor/subminer-yomitan`
|
||||
- Do not change signing/packaging identifiers unless the task explicitly requires it
|
||||
|
||||
## Release / PR Notes
|
||||
|
||||
- User-visible PRs need one fragment in `changes/*.md`
|
||||
- CI enforces `bun run changelog:lint` and `bun run changelog:pr-check`
|
||||
- PR review helpers:
|
||||
- `gh pr view --json number,title,url --jq '"PR #\\(.number): \\(.title)\\n\\(.url)"'`
|
||||
- `gh api repos/:owner/:repo/pulls/<num>/comments --paginate`
|
||||
|
||||
## Runtime Notes
|
||||
|
||||
- Use Codex background for long jobs; tmux only when persistence/interaction is required
|
||||
- CI red: `gh run list/view`, rerun, fix, repeat until green
|
||||
- TypeScript: keep files small; follow existing patterns
|
||||
- Swift: use workspace helper/daemon; validate `swift build` + tests
|
||||
|
||||
@@ -0,0 +1,515 @@
|
||||
# Changelog
|
||||
|
||||
## v0.14.0 (2026-05-12)
|
||||
|
||||
### Added
|
||||
|
||||
- **Character Dictionary:** Added AniList-based character dictionary selection for resolving title mismatches — open it in-app with the new `Ctrl+Alt+A` shortcut or from the CLI with `subminer dictionary --candidates` / `--select`. Series-scoped overrides replace stale entries in the merged dictionary.
|
||||
- **Primary Subtitle Bar:** Added a `V` shortcut and mpv plugin binding to toggle the primary subtitle bar without affecting mpv's native subtitle visibility.
|
||||
- **Texthooker:** Added `subminer texthooker -o` and a tray menu item to open the local texthooker page in the default browser.
|
||||
|
||||
### Changed
|
||||
|
||||
- **mpv Plugin Setup:** SubMiner-managed launches now automatically inject the bundled mpv plugin when no global plugin is installed. The legacy plugin removal prompt appears before mpv starts so users can trash the old global plugin and relaunch immediately.
|
||||
- **Tray Menu:** Replaced the "Open Overlay" menu item with "Open Help", which opens the session help modal.
|
||||
- **Stats Exclusions:** Vocabulary exclusions now persist in the immersion database and import any existing browser-local exclusions on first load.
|
||||
- **Config Example:** The generated example config now lists every built-in default keybinding, with regression coverage ensuring they compile, dispatch in the overlay, and register through the mpv plugin.
|
||||
- **Default Startup Options:** Texthooker startup and subtitle/annotation WebSocket servers are now disabled by default. Fresh installs also get updated primary subtitle style defaults: a Japanese font stack, transparent subtitle backgrounds, stronger text shadows, and teal N4/fourth-band coloring.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **JLPT Underlines:** Restored persistent JLPT underlines on subtitle tokens; they now keep their JLPT color after dictionary lookups and when a token also carries a known-word or frequency annotation color.
|
||||
- **Hyprland Fullscreen Overlay:** Fixed fullscreen transitions on Hyprland so mpv fullscreen changes refresh overlay geometry, reassert topmost stacking, and keep hover-pause working through resize/toggle cycles. The overlay now aligns exactly with the mpv window, disables floating-window decoration on exact placement, and no longer pins across workspaces.
|
||||
- **macOS Overlay:** Kept the overlay visible and interactive during transient tracker refreshes and while mpv is the active tracked window. Fixed z-order so the overlay stays above mpv but behind other foreground windows.
|
||||
- **Subtitle Annotation Colors:** Fixed annotated token colors so `subtitleStyle` typography is preserved and higher-priority known-word and frequency colors correctly take precedence over JLPT colors. Added a subtle brightness lift for hover states so transparent hover backgrounds still show a visible affordance. Hid the browser focus ring on the overlay surface so focused overlays no longer show a viewport border.
|
||||
- **Grammar and Particle Annotation Filters:** Suppressed annotation styling (N+1, JLPT, frequency, name) for grammar-only tokens including auxiliary inflection fragments like `れる`/`れた`, polite copula tails like `です`/`ですよ`, negative copula phrases like `じゃないですか`, and standalone particles like `に`; lexical forms like `くれる` remain eligible.
|
||||
- **Interjection and Existence Verb Filters:** Suppressed annotations for ハァ-style interjections and `ある`/`有る` existence verbs; known-word highlighting still applies for `ある`.
|
||||
- **Known-Word Compound Tokens:** Preserved Yomitan compound token boundaries for known-word highlighting so known component words no longer color a larger unknown compound green.
|
||||
- **Kana Token Annotations:** Stopped kana-only tokens from being selected as N+1 targets or receiving N+1, JLPT, or frequency annotation metadata.
|
||||
- **Subtitle Bar Modes:** Changed `v` to cycle the primary subtitle bar through visible, hover, and hidden modes with OSD feedback. A new `subtitleStyle.primaryDefaultMode` config option sets the startup default independently of secondary subtitles.
|
||||
- **Subtitle Keybindings:** Fixed the default replay/next subtitle keybindings by moving session help to `Ctrl/Cmd+/`, freeing `Ctrl+Shift+H` and `Ctrl+Shift+L` for subtitle playback controls. The mpv plugin now registers shifted letter chords correctly so `Ctrl+Shift+L` reaches play-next-subtitle; play-next also starts playback from a paused state before re-pausing at subtitle end.
|
||||
- **Yomitan Popup Precedence:** Fixed keyboard-only Yomitan popup controls so popup shortcuts take precedence over overlay keybindings like `j`.
|
||||
- **Subtitle Prefetch:** Kept annotation prefetch running after immediate cache-hit renders so upcoming subtitle colors stay ready.
|
||||
- **Frequency Highlighting:** Fixed frequency highlighting for ordinal prefix-noun tokens like `第二` so JPDB ranks are preserved in subtitle annotations.
|
||||
- **Known-Word Refresh After Mining:** Refreshed the current subtitle after successful card mining so newly known words recolor immediately.
|
||||
- **Linux Multi-Line Copy:** Fixed multi-line subtitle copy on Linux so the overlay handles the follow-up digit selection locally, eliminating the timeout.
|
||||
- **mpv Crash Notifications:** Stopped mpv from owning long-running SubMiner subprocesses during shutdown, preventing desktop crash notifications when closing video.
|
||||
- **mpv Buffering Reload:** Kept the overlay alive across same-media reloads during buffering, avoiding duplicate startup gates and AniSkip lookups.
|
||||
- **mpv Playlist Navigation:** Playlist navigation now reuses the running overlay without repeating the pause-until-ready warmup gate.
|
||||
- **Anki Manual Updates:** Manual clipboard subtitle updates now replace sentence audio and media even when audio overwrite is disabled, while preserving existing word audio.
|
||||
- **AniList Progress Tracking:** Fixed post-watch progress to check on mpv time-position updates using the fresh mpv position, wired manual mark-watched to force a progress sync, and filled missing episode metadata from the filename parser. Prevented duplicate post-watch writes during concurrent checks and preserved manual watched marks when sync fails.
|
||||
- **AniList Setup:** Prevented config reload from opening the setup window during playback when token storage is unavailable; stopped setup from reporting success when encrypted token persistence fails.
|
||||
- **AniList Linux Keyring:** Retried keyring availability after transient GNOME libsecret startup failures so stored tokens load once the keyring becomes available.
|
||||
- **Stats Daemon:** Fixed stats background mode to route through the isolated stats daemon and deferred in-app stats startup when a daemon is already running, so video launches no longer fail when the stats port is in use.
|
||||
- **Stats Session Detail:** Fixed recent session detail pages showing "Media not found" before lifetime media summaries are available.
|
||||
- **Hover Background Config:** Accepted `subtitleStyle.hoverBackground` as an alias for `subtitleStyle.hoverTokenBackgroundColor` so setting it to `transparent` removes hover token backgrounds.
|
||||
- **Managed Playback Exit:** Launcher-managed playback now exits the background SubMiner app when the video closes; explicit background launches remain persistent.
|
||||
- **Multi-Mine Shortcuts:** Multi-line subtitle mining now accepts follow-up number-row digits even when the original shortcut modifiers are still held.
|
||||
- **Jellyfin Setup:** Improved setup with recent server selection and inline authentication feedback; added a tray "Jellyfin Discovery" toggle for runtime-only cast discovery.
|
||||
- **Subtitle POS Filtering:** Used Yomitan `wordClasses` metadata for subtitle part-of-speech filtering and backfilled blank MeCab POS detail fields during parser enrichment.
|
||||
|
||||
### Docs
|
||||
|
||||
- Improved the docs homepage with canonical URLs and a cleaner sitemap for better search engine indexing.
|
||||
|
||||
<details>
|
||||
<summary>Internal changes</summary>
|
||||
|
||||
### Internal
|
||||
|
||||
- Replaced the changelog renderer with an AI polish pass that merges related fragments and writes user-facing release notes. `CHANGELOG.md` keeps internal items in a collapsed `<details>` block; GitHub release notes omit them entirely.
|
||||
- Release CI no longer auto-builds pending `changes/*.md` fragments on tag. Tagging now fails fast if fragments remain — run `bun run changelog:build` (requires the `claude` CLI) and commit before tagging.
|
||||
|
||||
</details>
|
||||
|
||||
## v0.12.0 (2026-04-11)
|
||||
|
||||
### Changed
|
||||
|
||||
- Overlay: Added configurable overlay shortcuts for session help, controller select, and controller debug actions.
|
||||
- Overlay: Added mpv/plugin and CLI routing for session help, controller utilities, and subtitle sidebar toggling through the shared session-action path.
|
||||
- Overlay: Improved dedicated overlay modal retry and focus handling for runtime options, Jimaku, session help, controller tools, and the playlist browser.
|
||||
- Overlay: Fixed controller configuration and controller debug shortcut opens so configured bindings bring up their modals again instead of tripping renderer recovery.
|
||||
- Stats: Sessions are rolled up per episode within each day, with a bulk delete that wipes every session in the group.
|
||||
- Stats: Trends add a 365-day range next to the existing 7d/30d/90d/all options.
|
||||
- Stats: Library detail view gets a delete-episode action that removes the video and all its sessions.
|
||||
- Stats: Vocabulary Top 50 tightens the word/reading column so katakana entries no longer push the scores off screen.
|
||||
- Stats: Episode detail hides card events whose Anki notes have been deleted, instead of showing phantom mining activity.
|
||||
- Stats: Trend and watch-time charts share a unified theme with horizontal gridlines and larger ticks for legibility.
|
||||
- Stats: Overview, Library, Trends, Sessions, and Vocabulary now use generic "title" wording so YouTube videos and anime live comfortably side by side in the dashboard.
|
||||
- Stats: Session timeline no longer plots seek-forward/seek-backward markers — they were too noisy on sessions with lots of rewinds.
|
||||
- Stats: Replaced the "Library — Per Day" section on the Stats → Trends page with a "Library — Summary" section. The new section shows a top-10 watch-time leaderboard chart and a sortable per-title table (watch time, videos, sessions, cards, words, lookups, lookups/100w, date range), all scoped to the current date range selector.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Overlay: Fixed overlay drag-and-drop routing so dropping external subtitle files like `.ass` onto mpv still loads them when the overlay is visible.
|
||||
- Overlay: Addressed the latest CodeRabbit follow-ups on PR #49, including generation-scoped Lua session binding names, stricter session command validation, session-help shortcut visibility, the numeric-selection key guard, stats-overlay startup classification, and safer session-binding persistence.
|
||||
- Overlay: Addressed the latest CodeRabbit follow-ups on the Windows overlay flow, including exact mpv target resolution, lower-overlay helper arguments, Win32 failure detection, and overlay cleanup on tracker loss.
|
||||
- Overlay: Fixed Windows overlay z-order so the visible subtitle overlay stops staying above unrelated apps after mpv loses focus.
|
||||
- Overlay: Fixed Windows overlay tracking to use native window polling and owner/z-order binding, which keeps the subtitle overlay aligned to the active mpv window more reliably.
|
||||
- Overlay: Fixed Windows overlay hide/restore behavior so minimizing mpv immediately hides the overlay and restoring mpv brings it back on top of the mpv window without requiring a click.
|
||||
- Overlay: Fixed stats overlay layering so the in-player stats page now stays above mpv and the subtitle overlay while it is open.
|
||||
- Overlay: Fixed Windows subtitle overlay stability so transient tracker misses and restore events keep the current subtitle visible instead of waiting for the next subtitle line.
|
||||
- Overlay: Fixed Windows focus handoff from the interactive subtitle overlay back to mpv so the overlay no longer drops behind mpv and briefly disappears.
|
||||
- Overlay: Fixed Windows visible-overlay startup so it no longer briefly opens as an interactive or opaque surface before the tracked transparent overlay state settles.
|
||||
- Overlay: Fixed spurious auto-pause after overlay visibility recovery and window resize so the overlay no longer pauses mpv until the pointer genuinely re-enters the subtitle area.
|
||||
- Overlay: Fixed Windows secondary subtitle hover mode so the expanded hover hit area no longer blocks the native minimize, maximize, and close buttons.
|
||||
- Overlay: Fixed Windows Yomitan popup focus loss after closing nested lookups so the original popup stays interactive instead of falling through to mpv.
|
||||
- Stats: Fixed immersion-tracker timestamp handling under Bun/libsql so library rows, session timelines, and lifetime summaries keep real wall-clock millisecond values instead of truncating to invalid negative timestamps.
|
||||
- Mpv Plugin: Fixed the mpv Lua plugin so hover and environment modules no longer use the `goto continue` pattern that can fail to parse on some user Lua runtimes.
|
||||
|
||||
### Internal
|
||||
|
||||
- Release: Added a dedicated beta/rc prerelease GitHub Actions workflow that publishes GitHub prereleases without consuming pending changelog fragments or updating AUR.
|
||||
- Release: Added prerelease note generation so beta and release-candidate tags can reuse the current pending `changes/*.md` fragments while leaving stable changelog publication for the final release cut.
|
||||
|
||||
## v0.11.2 (2026-04-07)
|
||||
|
||||
### Changed
|
||||
|
||||
- Launcher: Replaced the launcher-only fullscreen toggle with `mpv.launchMode` so SubMiner-managed mpv playback can start in normal, maximized, or fullscreen mode.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Launcher: Fixed launcher-managed mpv spawning to force an explicit X11 GPU path when Wayland trackers are unavailable.
|
||||
- Launcher: Local playback now promotes a single unlabeled external subtitle sidecar to the primary slot instead of leaving mpv's embedded English auto-selection in place.
|
||||
- Release: Fixed Linux AppImage startup packaging so Chromium child relaunches can resolve the bundled `libffmpeg.so` instead of crash-looping on startup.
|
||||
|
||||
## v0.11.1 (2026-04-04)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Release: Linux packaged builds now expose the canonical `SubMiner` app identity to Electron's startup metadata so native Wayland compositors stop reporting the window class/app-id as lowercase `subminer`.
|
||||
- Linux: Linux now restores the runtime options, Jimaku, and Subsync shortcuts after the Electron 39 regression by routing those actions through the overlay's mpv/IPC shortcut path.
|
||||
|
||||
## v0.11.0 (2026-04-03)
|
||||
|
||||
### Added
|
||||
|
||||
- Overlay: Added a playlist browser overlay modal for browsing sibling video files and the live mpv queue during playback.
|
||||
- Overlay: Added the default `Ctrl+Alt+P` keybinding to open the playlist browser and manage queue order without leaving playback.
|
||||
|
||||
### Changed
|
||||
|
||||
- Setup: Made mpv plugin installation mandatory in the first-run setup flow, removed the skip path, and kept Finish disabled until the plugin is installed.
|
||||
- Setup: Clarified that the mpv plugin requirement applies to setup on every platform, while the optional `SubMiner mpv` shortcut remains the recommended Windows playback entry point.
|
||||
- Launcher: Streamlined Windows setup and config by making the `SubMiner mpv` shortcut self-contained and keeping `mpv.executablePath` as the simple fallback when `mpv.exe` is not on `PATH`.
|
||||
- Overlay: Changed fresh-install default config to keep texthooker and stats from auto-opening browser tabs.
|
||||
- Overlay: Changed fresh-install default config to enable AnkiConnect, Discord Rich Presence, subtitle-sidebar, and Yomitan-popup auto-pause by default, while disabling controller input by default.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Main: Resolve the YouTube playback socket path lazily so startup honors CLI and config overrides.
|
||||
- Main: Add regression coverage for the lazy socket-path lookup during Windows mpv startup.
|
||||
- Main: Keep integrated `--start --texthooker` launches on the full app-ready startup path so the texthooker page and websocket servers start together during normal playback startup.
|
||||
- Main: Stop the mpv/plugin auto-start flow from spawning a separate standalone texthooker helper during normal `subminer <video>` launches.
|
||||
- Overlay: Keep tracked macOS visible overlays click-through by default so subtitle sidebar passthrough works immediately without requiring a subtitle hover cycle first.
|
||||
- Overlay: Add regression coverage for the macOS visible-overlay passthrough default.
|
||||
- Anilist: Stop AniList post-watch from sending a second progress update when the current episode was already satisfied by a ready retry item in the same watch-completion pass.
|
||||
- Anilist: Add regression coverage for the retry-queue plus live-update duplicate path.
|
||||
- Overlay: Fixed Kiku duplicate grouping to reuse duplicate note IDs from both generic sentence-card creation and Yomitan popup mining instead of running extra duplicate scans after add.
|
||||
- Overlay: Fixed the Yomitan popup mining flow to add cards in the background while keeping the stock popup progress feedback, then pause playback and close the lookup popup before the Kiku merge modal opens.
|
||||
- Overlay: Fixed configured subtitle-jump keybindings so backward and forward subtitle seeks keep playback paused when invoked from a paused state.
|
||||
- Launcher: Fixed the Windows `SubMiner mpv` shortcut and `SubMiner.exe --launch-mpv` flow to launch mpv with SubMiner's required default args directly instead of requiring an `mpv.conf` profile named `subminer`.
|
||||
- Launcher: Clarified the Windows install and usage docs so the shortcut path is documented as self-contained, while the optional `subminer` mpv profile remains available for manual mpv launches.
|
||||
- Launcher: Hardened the first-run setup blocker copy and stale custom-scheme handling so setup messages stay aligned with config, plugin, and dictionary readiness.
|
||||
- Launcher: Fixed the Windows `SubMiner mpv` shortcut idle launch so loading a video after opening the shortcut keeps mpv in the expected SubMiner-managed session, auto-starts the overlay, and re-arms subtitle auto-selection for the newly opened file.
|
||||
- Launcher: Removed the redundant `.` subtitle search path from the Windows shortcut launch args and deduped repeated subtitle source tracks in the manual sync picker so duplicate external subtitle entries no longer appear from the shortcut path.
|
||||
- Playback: Fixed managed local playback so duplicate startup-ready retries no longer unpause media after a later manual pause on the same file.
|
||||
- Playback: Fixed managed local subtitle auto-selection so local files reuse configured primary and secondary subtitle language priorities instead of staying on mpv's initial `sid=auto` guess.
|
||||
- Launcher: Added a blank-by-default `mpv.executablePath` override for Windows playback so users can point SubMiner at `mpv.exe` when it is not on `PATH`.
|
||||
- Launcher: Kept the Windows shortcut and `--launch-mpv` flow simple by preserving PATH auto-discovery as the default and exposing the override in first-run setup.
|
||||
- Launcher: Added `windows` as a recognized launcher backend option and auto-detection target on Windows.
|
||||
- Launcher: Honored `SUBMINER_YTDLP_BIN` consistently across YouTube playback URL resolution, track probing, subtitle downloads, and metadata probing.
|
||||
- Launcher: Kept the first-run setup window from navigating away on unexpected URLs.
|
||||
- Launcher: Made Windows mpv honor an explicitly configured executable path instead of silently falling back to PATH.
|
||||
- Launcher: Hardened `--launch-mpv` parsing and Windows binary resolution so valueless flags do not swallow media targets and symlinked launcher installs do not short-circuit PATH lookup.
|
||||
- Launcher: Fixed first-run setup blocking playback on macOS when the SubMiner mpv plugin was already installed at the canonical `~/.config/mpv` path.
|
||||
- Launcher: Fixed setup gating so stale cancelled setup state no longer prevents playback when the canonical mpv plugin entrypoint already exists.
|
||||
- Playback: Prevented stale async playlist-browser subtitle rearm callbacks from overriding newer subtitle selections during rapid file changes.
|
||||
|
||||
### Docs
|
||||
|
||||
- Docs Site: Added a dedicated Subtitle Sidebar guide and linked it from the homepage and configuration docs.
|
||||
- Docs Site: Linked Jimaku integration from the homepage to its dedicated docs page.
|
||||
- Docs Site: Refreshed docs-site theme tokens and hover/selection styling for the updated pages.
|
||||
|
||||
### Internal
|
||||
|
||||
- Release: Retried AUR clone and push operations in the tagged release workflow.
|
||||
- Release: Kept GitHub Releases green when AUR publish flakes and needs manual follow-up.
|
||||
- Release: Updated Electron to 39.8.6 and pinned patched transitive build dependencies to clear the reported high-severity audit findings.
|
||||
|
||||
## v0.10.0 (2026-03-29)
|
||||
|
||||
### Changed
|
||||
|
||||
- Integrations: Replaced the deprecated Discord Rich Presence wrapper with the maintained `@xhayper/discord-rpc` package.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Stats: Fixed stats startup so the immersion tracker can run when `Bun.serve` is unavailable.
|
||||
- Stats: Stats server now falls back to a Node `http` listener in Electron/runtime paths that do not expose Bun.
|
||||
- Overlay: Fixed the macOS visible-overlay toggle path so manual hides stay hidden and the plugin uses the explicit visible-overlay toggle command.
|
||||
- Subtitle Sidebar: Restored macOS mpv passthrough while the overlay subtitle sidebar is open so clicks outside the sidebar can refocus mpv and keep native keybindings working.
|
||||
|
||||
### Internal
|
||||
|
||||
- Release: Added a maintained source coverage lane that shards Bun coverage one test file at a time and merges LCOV output into `coverage/test-src/lcov.info`.
|
||||
- Release: CI and release quality-gate now upload the merged source-lane LCOV artifact for inspection.
|
||||
- Runtime: Extracted remaining inline runtime logic from `src/main.ts` into dedicated runtime modules and composer helpers.
|
||||
- Runtime: Added focused regression tests for the extracted runtime/composer boundaries.
|
||||
- Runtime: Updated task tracking notes to mark TASK-238.6 complete and confirm follow-on boot-phase split can be deferred.
|
||||
- Runtime: Split `src/main.ts` boot wiring into dedicated `src/main/boot/services.ts`, `src/main/boot/runtimes.ts`, and `src/main/boot/handlers.ts` modules.
|
||||
- Runtime: Added focused tests for the new boot-phase seams and kept the startup/typecheck/build verification lanes green.
|
||||
- Runtime: Updated internal architecture/task docs to record the boot-phase split and new ownership boundary.
|
||||
|
||||
## v0.9.3 (2026-03-25)
|
||||
|
||||
### Changed
|
||||
|
||||
- Launcher: Moved YouTube primary subtitle language defaults to `youtube.primarySubLanguages`.
|
||||
- Launcher: Removed the placeholder YouTube subtitle retime step and now uses downloaded primary subtitle tracks directly, so there is no fake path rewrite before playback/sidebar loading.
|
||||
- YouTube: Removed the `src/core/services/youtube/retime` helper and its tests after retiring the internal retime strategy.
|
||||
- Docs: Clarified optional `alass` / `ffsubsync` subtitle-sync requirements and setup steps, including fallback behavior when sync tools are absent.
|
||||
- Launcher: Removed the old `youtubeSubgen.primarySubLanguages` config path from the generated config and docs.
|
||||
|
||||
## v0.9.2 (2026-03-25)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Overlay: Fixed overlay pointer tracking so Windows click-through toggles immediately when the cursor enters or leaves subtitle regions, without waiting for a later hover resync.
|
||||
- Overlay: Fixed Windows overlay window tracking on scaled displays by converting native tracked window bounds to Electron DIP coordinates before applying overlay bounds.
|
||||
- Launcher: Fixed Windows direct `--youtube-play` startup so MPV boots reliably, stays paused until the app-owned subtitle flow is ready, and reuses an already-running SubMiner instance when available.
|
||||
- Launcher: Fixed standalone Windows `--youtube-play` sessions so closing MPV fully exits SubMiner instead of leaving hidden overlay windows or a background process behind.
|
||||
- Overlay: Fixed `subminer <youtube-url>` on Linux so the YouTube playback flow waits for Yomitan to load before creating the overlay window, avoiding the broken lookup popup state that previously required a manual overlay refresh.
|
||||
|
||||
## v0.9.1 (2026-03-24)
|
||||
|
||||
### Changed
|
||||
|
||||
- Release: Reduced packaged release size by excluding duplicate `extraResources` payload and pruning docs, tests, sourcemaps, and other source-only files from Electron bundles.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Overlay: Restored controller navigation and lookup/mining controls while the subtitle sidebar is open, while keeping true modal dialogs blocking controller actions.
|
||||
- Tokenizer: Fixed subtitle annotation clearing so explanatory contrast endings like `んですけど` are excluded consistently across the shared tokenizer filter and annotation stage.
|
||||
|
||||
## v0.9.0 (2026-03-23)
|
||||
|
||||
### Added
|
||||
|
||||
- Docs: Added a new WebSocket / Texthooker API and integration guide covering WebSocket payloads, custom client patterns, mpv plugin automation, and webhook-style relay examples. Linked from configuration and mining workflow docs for easier discovery.
|
||||
|
||||
### Changed
|
||||
|
||||
- Launcher: Added an app-owned YouTube subtitle flow that pauses mpv, uses absPlayer-style YouTube timedtext parsing/conversion to download subtitle tracks, and injects them as external files before playback resumes.
|
||||
- Launcher: Changed YouTube subtitle startup to auto-load the best-available primary and secondary subtitle tracks at launch instead of forcing the picker modal first. Secondary subtitle failures no longer block playback resume.
|
||||
- Launcher: Added `Ctrl+Alt+C` as the default keybinding to manually open the YouTube subtitle picker during active YouTube playback.
|
||||
- Launcher: Added yt-dlp metadata probing so YouTube playback and immersion tracking record canonical video title and channel metadata.
|
||||
- Launcher: Stopped forcing `--ytdl-raw-options=` before user-provided mpv options so existing YouTube cookie integrations in user `--args` are no longer clobbered.
|
||||
- Launcher: Disabled mpv native YouTube subtitle auto-loading for the app-owned flow so injected external subtitle files remain authoritative.
|
||||
- Launcher: Added OSD status messages for YouTube playback startup, subtitle acquisition, and subtitle loading so the flow stays visible before and during the picker.
|
||||
- Subtitle Sidebar: Added startup-auto-open controls and resume positioning improvements so the sidebar jumps directly to the first resolved active cue.
|
||||
- Subtitle Sidebar: Improved subtitle prefetch and embedded overlay passthrough sync so sidebar and overlay subtitle states stay consistent across media transitions.
|
||||
- Subtitle Sidebar: Updated scroll handling, embedded layout styling, and active-cue visual behavior.
|
||||
- Stats: Stats Library tab now displays YouTube video title, channel name, and channel thumbnail for YouTube media entries, with retry logic to fill in metadata that arrives after initial load.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Launcher: Fixed Anki media mining for mpv YouTube streams by unwrapping the stream URL so audio and screenshot capture work correctly for YouTube playback sessions.
|
||||
- Immersion: Fixed YouTube media path handling in the immersion runtime and tracking so YouTube sessions record correct media references, AniList guessing skips YouTube URLs, and post-watch state transitions do not fire for YouTube media.
|
||||
- Launcher: Fixed startup-launched YouTube playback so primary subtitle overlay updates continue after auto-load completes.
|
||||
- Launcher: Fixed auto-loaded YouTube primary subtitles so parsed cues appear in the subtitle sidebar without needing a manual picker retry.
|
||||
- Launcher: Fixed the YouTube picker to guard against duplicate subtitle submissions and tightened YouTube URL detection so follow-up runtime flows only treat real YouTube hosts as YouTube playback.
|
||||
- Launcher: Fixed primary subtitle failure notifications being shown while app-owned YouTube subtitle probing and downloads are still in flight.
|
||||
- Launcher: Preserved existing authoritative YouTube subtitle tracks when available; downloaded tracks are used only to fill missing sides, and native mpv secondary subtitle rendering is hidden so the overlay remains the sole secondary display.
|
||||
|
||||
## v0.8.0 (2026-03-22)
|
||||
|
||||
### Added
|
||||
|
||||
- Overlay: Added the subtitle sidebar feature with a new `subtitleSidebar` configuration surface and rendered sidebar modal with cue list rendering, click-to-seek, active-cue highlighting, and embedded layout support.
|
||||
- IPC: Added sidebar snapshot plumbing between renderer and main process for overlay/sidebar synchronization.
|
||||
|
||||
### Changed
|
||||
|
||||
- Config: Added hot-reloadable sidebar options for enablement, layout, visibility, typography, opacity, sizing, and interaction behavior (`autoOpen`, `pauseOnHover`, `autoScroll`, toggle key).
|
||||
- Docs: Added full `subtitleSidebar` documentation coverage, including sample config, option table, and toggle shortcut notes.
|
||||
- Runtime: Improved subtitle prefetch/rendering flow so sidebar and overlay subtitle states stay in sync across media transitions.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Overlay: Kept sidebar cue tracking stable across playback transitions and timing edge cases.
|
||||
- Overlay: Improved sidebar resume/start behavior to jump directly to the first resolved active cue.
|
||||
- Overlay: Stopped stale subtitle refreshes from regressing active-cue and text state.
|
||||
|
||||
## v0.7.0 (2026-03-19)
|
||||
|
||||
### Added
|
||||
|
||||
- Immersion: Added Mine Word, Mine Sentence, and Mine Audio buttons to word detail example lines in the stats dashboard.
|
||||
- Immersion: Mine Word creates a full Yomitan card (definition, reading, pitch accent) via the hidden search page bridge, then enriches with sentence audio, screenshot, and metadata extracted from the source video.
|
||||
- Immersion: Mine Sentence and Mine Audio create cards directly with appropriate Lapis/Kiku flags, sentence highlighting, and media from the source file.
|
||||
- Immersion: Media generation (audio + image/AVIF) runs in parallel and respects all AnkiConnect config options.
|
||||
- Immersion: Added word exclusion list to the Vocabulary tab with localStorage persistence and a management modal.
|
||||
- Immersion: Fixed truncated readings in the frequency rank table (e.g. お前 now shows おまえ instead of まえ).
|
||||
- Immersion: Clicking a bar in the Top Repeated Words chart now opens the word detail panel.
|
||||
- Immersion: Secondary subtitle text is now stored alongside primary subtitle lines for use as translation when mining cards from the stats page.
|
||||
- Stats: Added `subminer stats -b` to start or reuse a dedicated background stats server without blocking normal SubMiner instances.
|
||||
- Stats: Added `subminer stats -s` to stop the dedicated background stats server without closing browser tabs.
|
||||
- Stats: Stats server startup now reuses a running background stats daemon instead of trying to bind a second local server in another SubMiner instance.
|
||||
- Launcher: Added launcher passthrough for `-a/--args` so mpv receives raw extra launch flags (`--fs`, `--ytdl-format`, custom audio/video settings, etc.) from the `subminer` command.
|
||||
- Launcher: Added `subminer stats` to launch the local stats dashboard, force-start the stats server on demand, and open the dashboard in your browser.
|
||||
- Launcher: Added `subminer stats cleanup` to backfill vocabulary metadata and prune stale or excluded immersion rows on demand.
|
||||
- Launcher: Added `stats.autoOpenBrowser` so browser launch after `subminer stats` can be enabled or disabled explicitly.
|
||||
- Immersion: Added a local stats dashboard for immersion tracking with Overview, Anime, Trends, Vocabulary, and Sessions views.
|
||||
- Immersion: Added anime progress, episode completion, Anki card links, and occurrence drill-down across the stats dashboard.
|
||||
- Immersion: Added richer session timelines with new-word activity, cumulative totals, and pause/seek/card event markers.
|
||||
- Immersion: Added completed-episodes and completed-anime totals to the Overview tracking snapshot.
|
||||
|
||||
### Changed
|
||||
|
||||
- Anki: Changed known-word cache settings to live under `ankiConnect.knownWords` instead of mixing them into `ankiConnect.nPlusOne`.
|
||||
- Anki: Kept legacy `ankiConnect.nPlusOne` known-word keys and older `ankiConnect.behavior.nPlusOne*` keys as deprecated compatibility fallbacks.
|
||||
- Stats: Added session deletion to the Sessions tab with the same confirmation prompt used by anime episode/session deletes, and removed all associated session rows from the stats database.
|
||||
- Immersion: Kept immersion tracking history by default while preserving daily/monthly rollup maintenance.
|
||||
- Immersion: Added exact lifetime summary reads for overview/anime/media stats so dashboard totals no longer depend on rescanning raw telemetry.
|
||||
- Immersion: Reduced tracker storage overhead by removing duplicated subtitle text from subtitle-line event payloads.
|
||||
- Immersion: Deduplicated episode cover-art blobs through a shared blob store and updated cover-art reads/writes to resolve shared images correctly.
|
||||
- Immersion: Added indexes for large-history session, telemetry, vocabulary, kanji, and cover-art queries to keep dashboard reads fast as the SQLite database grows.
|
||||
- Immersion: Renamed the stats dashboard's Anime tab to Library so the media browser label matches non-anime sources like YouTube and other yt-dlp-backed content.
|
||||
- Anilist: Standardized episode completion threshold by introducing `DEFAULT_MIN_WATCH_RATIO` and using it for both local watched state transitions and AniList post-watch progress updates.
|
||||
- Anilist: Episode auto-marking now uses the same threshold as AniList (`85%`), removing divergent completion behavior.
|
||||
- Overlay: Excluded interjections and sound-effect tokens from subtitle annotation styling so they no longer inherit misleading lexical highlight treatment while still remaining visible and hoverable as plain subtitle tokens.
|
||||
- Overlay: Expanded subtitle annotation noise filtering to also strip annotation metadata from standalone grammar-only helper tokens such as particles, auxiliaries, adnominals, common explanatory endings like `んです` / `のだ`, and merged trailing quote-particle forms like `...って` while keeping them tokenized for hover lookup.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Launcher: Fixed mpv Lua plugin binary auto-detection on Linux to also search `/usr/bin/subminer` and `/usr/local/bin/subminer` (lowercase), matching the conventional Unix wrapper name used by packaged installs such as the AUR package.
|
||||
- Stats: Fixed the in-app stats overlay so it connects to the configured `stats.serverPort` instead of falling back to the default port.
|
||||
- Overlay: Fixed subtitle frequency tagging for merged lookup-backed tokens like `陰に` by falling back to exact surface-form Yomitan frequencies when the normalized headword lookup misses.
|
||||
- Overlay: Fixed MeCab merged-token position mapping across line breaks so merged content-plus-particle tokens like `陰に` keep their matched Yomitan frequency instead of inheriting shifted POS tags.
|
||||
- Overlay: Fixed grouped frequency parsing in both Yomitan and fallback frequency-dictionary lookups so display values like `118,121` use the leading rank instead of collapsing the rank and occurrence count into `118121`.
|
||||
- Overlay: Fixed frequency-rank ingestion to ignore Yomitan dictionaries explicitly marked `occurrence-based`, so raw occurrence counts are no longer treated as subtitle rank values.
|
||||
- Overlay: Fixed inflected headword frequency tagging to prefer ranks from the selected Yomitan `termsFind` popup entry itself, ordered by configured dictionary priority, so forms like `潜み` use primary-dictionary ranks like `4073` before falling back to lower-priority raw lemma metadata such as `CC100`.
|
||||
- Overlay: Fixed annotation-stage frequency filtering so exact kanji noun tokens like `者` keep their matched rank even when MeCab labels them `名詞/非自立`, instead of dropping the highlight after scan-time frequency lookup succeeds.
|
||||
- Anki: Fixed repeated character-dictionary startup work by scheduling auto-sync only from mpv media-path changes instead of also re-triggering it from connection and media-title events for the same title.
|
||||
- Overlay: Fixed macOS fullscreen overlay stability by keeping the passive visible overlay from stealing focus, re-raising the overlay window when reasserting its macOS topmost level, and tolerating one transient macOS tracker/helper miss before hiding the overlay.
|
||||
- Overlay: Kept subtitle tokenization warmup one-shot for the lifetime of the app so later fullscreen/media churn on macOS does not replay the startup warmup gate after the first file is ready.
|
||||
- Overlay: Added a bounded macOS tracker loss-grace window so fullscreen enter/leave transitions do not immediately hide and reload the overlay when the helper briefly loses the mpv window.
|
||||
- Overlay: Skipped subtitle/tokenization refresh invalidation on character-dictionary auto-sync completion when the dictionary was already current, preventing startup flash/reload loops on unchanged media.
|
||||
- Stats: Fixed session stats so known-word counts track real known-word occurrences without collapsing subtitle-line gaps.
|
||||
- Stats: Fixed session word totals in session-facing stats views to prefer token counts when available, preventing known words from exceeding total words in the session chart.
|
||||
- Stats: Fixed the stats Vocabulary tab blank-screen regression caused by a hook-order crash after vocabulary data finished loading.
|
||||
- Anki: Fixed card-mine OSD feedback so the final mine result stops the Anki spinner first, then shows a single-line `✓`/`x` status without being overwritten by a later spinner tick.
|
||||
- Stats: Removed the misleading `New words` series from expanded session charts; session detail now shows only the real total-word and known-word lines.
|
||||
- Stats: Restored the cross-anime word table behavior in stats vocabulary surfaces so shared vocabulary entries no longer disappear or merge incorrectly across related media.
|
||||
- Stats: `subminer stats -b` now runs as a standalone background stats daemon instead of reusing the main SubMiner app process, so the overlay app can still be launched separately for normal video watching.
|
||||
- Stats: Dashboard word mining still works against the background daemon by using a short-lived hidden helper for the Yomitan add-note flow.
|
||||
- Stats: Load full session timelines by default in stats session detail views so long sessions preserve complete telemetry history instead of being truncated by a fixed sample limit.
|
||||
- Stats: Replaced heuristic stats word counts with Yomitan token counts, so session, media, anime, and trend subtitle totals now come directly from parsed subtitle tokens.
|
||||
- Stats: Updated stats UI labels and lookup-rate copy to refer to tokens instead of words where those counts are shown.
|
||||
- Overlay: Reduced repeated `Overlay loading...` popups on macOS when fullscreen tracker flaps briefly hide and recover the visible overlay.
|
||||
- Stats: Scaled expanded session-detail known-word charts to the session's actual percentage range so small changes no longer render as a nearly flat line.
|
||||
- Jlpt: Reduced JLPT dictionary startup log noise by summarizing duplicate surface-form collisions instead of logging one line per duplicate entry.
|
||||
|
||||
## v0.6.5 (2026-03-15)
|
||||
|
||||
### Internal
|
||||
|
||||
- Release: Seed the AUR checkout with the repo `.SRCINFO` template before rewriting metadata so tagged releases do not depend on prior AUR state.
|
||||
|
||||
## v0.6.4 (2026-03-15)
|
||||
|
||||
### Internal
|
||||
|
||||
- Release: Reworked AUR metadata generation to update `.SRCINFO` directly instead of depending on runner `makepkg`, fixing tagged release publishing for `subminer-bin`.
|
||||
|
||||
## v0.6.3 (2026-03-15)
|
||||
|
||||
### Changed
|
||||
|
||||
- Overlay: Expanded the `Alt+C` controller modal into an inline config/remap flow with preferred-controller saving and per-action learn mode for buttons, triggers, and stick directions.
|
||||
|
||||
### Internal
|
||||
|
||||
- Workflow: Hardened the `subminer-scrum-master` skill to explicitly answer whether docs updates and changelog fragments are required before handoff.
|
||||
- Release: Automate `subminer-bin` AUR package updates from the tagged release workflow.
|
||||
|
||||
## v0.6.2 (2026-03-12)
|
||||
|
||||
### Changed
|
||||
|
||||
- Config: Added `yomitan.externalProfilePath` to reuse another Electron app's Yomitan profile in read-only mode.
|
||||
- Config: SubMiner now reuses external Yomitan dictionaries/settings without writing back to that profile.
|
||||
- Config: Launcher-managed playback now respects `yomitan.externalProfilePath` and no longer forces first-run setup when external Yomitan is configured.
|
||||
- Config: SubMiner now seeds `config.jsonc` even when the default config directory already exists.
|
||||
- Config: First-run setup now allows zero internal dictionaries when `yomitan.externalProfilePath` is configured, and falls back to requiring at least one internal dictionary if that external profile is later removed.
|
||||
|
||||
## v0.6.1 (2026-03-12)
|
||||
|
||||
### Added
|
||||
|
||||
- Overlay: Added Chrome Gamepad API controller support for keyboard-only overlay mode, including configurable logical bindings for lookup, mining, popup navigation, Yomitan audio, mpv pause, d-pad fallback navigation, and slower smooth popup scrolling.
|
||||
- Overlay: Added `Alt+C` controller selection and `Alt+Shift+C` controller debug modals, with preferred controller persistence and live raw input inspection.
|
||||
- Overlay: Added a transient in-overlay controller-detected indicator when a controller is first found.
|
||||
- Overlay: Fixed stale keyboard-only token highlight cleanup when keyboard-only mode turns off or the Yomitan popup closes.
|
||||
|
||||
### Docs
|
||||
|
||||
- Install: Added Arch Linux AUR install docs for `subminer-bin` in the README and installation guide.
|
||||
|
||||
### Internal
|
||||
|
||||
- Config: add an enforced `verify:config-example` gate so checked-in example config artifacts cannot drift silently
|
||||
- Release: Fixed the release workflow token permissions so tagged builds can download `oven-sh/setup-bun` and publish artifacts again.
|
||||
|
||||
## v0.5.6 (2026-03-10)
|
||||
|
||||
### Fixed
|
||||
|
||||
- Dictionary: Persist merged character-dictionary MRU state as soon as a new retained set is built so revisits do not get dropped if later Yomitan import work fails, and skip merged dictionary rebuilds for reorder-only revisits when the retained anime set itself has not changed.
|
||||
- Startup: Fixed early Electron startup writing config and user data under a lowercase `~/.config/subminer` path instead of the canonical `~/.config/SubMiner` directory.
|
||||
- Overlay: Kept JLPT underline colors stable during Yomitan hover and selection states, even when tokens also use known, N+1, name-match, or frequency styling.
|
||||
|
||||
## v0.5.5 (2026-03-09)
|
||||
|
||||
### Changed
|
||||
|
||||
- Overlay: Added `f` as the default overlay fullscreen toggle and changed the default AniSkip intro-jump key to `Tab`.
|
||||
- Dictionary: Aligned AniList character dictionary generation more closely with the upstream reference by preserving duplicate shared names across characters, skipping characters without native Japanese names, restoring richer character info fields, and using upstream-style role mapping plus hint-aware kanji readings.
|
||||
- Startup: Ordered startup OSD messages so tokenization loads first, annotation loading appears next if still pending, and character dictionary sync progress waits until annotation loading finishes.
|
||||
- Dictionary: Added a visible startup OSD step for merged character-dictionary building so long rebuilds show progress before the later import/upload phase.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Dictionary: Fixed AniList media guessing for character dictionary auto-sync by using filename-only `guessit` input and preserving multi-part guessit titles instead of truncating them to the first segment.
|
||||
- Dictionary: Refresh the current subtitle after character dictionary auto-sync completes so newly imported character names highlight on the active line instead of waiting for the next subtitle change.
|
||||
- Dictionary: Show character dictionary auto-sync progress on the mpv OSD without sending desktop notifications.
|
||||
- Dictionary: Keep character dictionary auto-sync non-blocking during startup by letting snapshot/build work run in parallel and delaying only the Yomitan import/settings phase until current-media tokenization is already ready.
|
||||
- Overlay: Fixed visible overlay keyboard handling so pressing `Tab` still reaches mpv and triggers the default AniSkip skip-intro binding while the overlay has focus.
|
||||
- Plugin: Fix Windows mpv plugin binary override lookup so `SUBMINER_BINARY_PATH` still resolves to `SubMiner.exe` when no AppImage override is set.
|
||||
|
||||
## v0.5.3 (2026-03-09)
|
||||
|
||||
### Changed
|
||||
|
||||
- Release: Publish unsigned Windows `.exe` and `.zip` artifacts directly from release CI instead of routing them through SignPath.
|
||||
- Release: Added `bun run build:win:unsigned` for explicit local unsigned Windows packaging.
|
||||
|
||||
## 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.
|
||||
@@ -1,10 +1,9 @@
|
||||
.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 uninstall uninstall-linux uninstall-macos uninstall-windows print-dirs pretty lint ensure-bun generate-config generate-example-config dev-start dev-start-macos dev-watch dev-watch-macos dev-toggle dev-stop
|
||||
|
||||
APP_NAME := subminer
|
||||
THEME_SOURCE := assets/themes/subminer.rasi
|
||||
LAUNCHER_OUT := dist/launcher/$(APP_NAME)
|
||||
THEME_FILE := subminer.rasi
|
||||
PLUGIN_CONF := plugin/subminer.conf
|
||||
|
||||
# Default install prefix for the wrapper script.
|
||||
PREFIX ?= $(HOME)/.local
|
||||
@@ -20,15 +19,11 @@ 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))
|
||||
MACOS_ZIP_SRC := $(firstword $(wildcard release/SubMiner-*.zip))
|
||||
APPIMAGE_SRC = $(firstword $(wildcard release/SubMiner-*.AppImage))
|
||||
MACOS_APP_SRC = $(firstword $(wildcard release/*.app release/*/*.app))
|
||||
MACOS_ZIP_SRC = $(firstword $(wildcard release/SubMiner-*.zip))
|
||||
PRERELEASE_NOTES := release/prerelease-notes.md
|
||||
|
||||
UNAME_S := $(shell uname -s 2>/dev/null || echo Unknown)
|
||||
ifeq ($(OS),Windows_NT)
|
||||
@@ -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,20 +62,18 @@ 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-plugin Install mpv Lua plugin and plugin config" \
|
||||
" install-windows Print Windows packaging/install guidance" \
|
||||
" generate-config Generate ~/.config/SubMiner/config.jsonc from centralized defaults" \
|
||||
"" \
|
||||
"Other targets:" \
|
||||
" deps Install JS dependencies (root + texthooker-ui)" \
|
||||
" deps Install JS dependencies (root + stats + texthooker-ui)" \
|
||||
" uninstall-linux Remove Linux install artifacts" \
|
||||
" uninstall-macos Remove macOS install artifacts" \
|
||||
" uninstall-windows Remove Windows mpv plugin artifacts" \
|
||||
" print-dirs Show resolved install locations" \
|
||||
" lint Lint stats (format check)" \
|
||||
"" \
|
||||
"Variables:" \
|
||||
" PREFIX=... Override wrapper install prefix (default: $$HOME/.local)" \
|
||||
@@ -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)"
|
||||
@@ -96,19 +104,25 @@ print-dirs:
|
||||
deps:
|
||||
@$(MAKE) --no-print-directory ensure-bun
|
||||
@bun install
|
||||
@cd stats && bun install --frozen-lockfile
|
||||
@cd vendor/texthooker-ui && bun install --frozen-lockfile
|
||||
|
||||
ensure-bun:
|
||||
@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
|
||||
@bun run format:stats
|
||||
|
||||
lint: ensure-bun
|
||||
@bun run lint:stats
|
||||
|
||||
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 +131,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
|
||||
|
||||
@@ -147,7 +162,15 @@ build-launcher:
|
||||
|
||||
clean:
|
||||
@printf '%s\n' "[INFO] Removing build artifacts"
|
||||
@rm -rf dist release
|
||||
@if [ -f "$(PRERELEASE_NOTES)" ]; then \
|
||||
PRERELEASE_NOTES_BACKUP="$$(mktemp -t subminer-prerelease-notes.XXXXXX)" && \
|
||||
cp "$(PRERELEASE_NOTES)" "$$PRERELEASE_NOTES_BACKUP" && \
|
||||
rm -rf dist release && \
|
||||
install -d release && \
|
||||
mv "$$PRERELEASE_NOTES_BACKUP" "$(PRERELEASE_NOTES)"; \
|
||||
else \
|
||||
rm -rf dist release; \
|
||||
fi
|
||||
@rm -f "$(BINDIR)/subminer" "$(BINDIR)/SubMiner.AppImage"
|
||||
|
||||
generate-config: ensure-bun
|
||||
@@ -155,21 +178,8 @@ generate-config: ensure-bun
|
||||
@bun run electron . --generate-config
|
||||
|
||||
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
|
||||
@@ -197,6 +207,8 @@ install-linux: build-launcher
|
||||
@install -m 0755 "$(LAUNCHER_OUT)" "$(BINDIR)/$(APP_NAME)"
|
||||
@install -d "$(LINUX_DATA_DIR)/themes"
|
||||
@install -m 0644 "./$(THEME_SOURCE)" "$(LINUX_DATA_DIR)/themes/$(THEME_FILE)"
|
||||
@install -d "$(LINUX_DATA_DIR)/plugin/subminer"
|
||||
@cp -R ./plugin/subminer/. "$(LINUX_DATA_DIR)/plugin/subminer/"
|
||||
@if [ -n "$(APPIMAGE_SRC)" ]; then \
|
||||
install -m 0755 "$(APPIMAGE_SRC)" "$(BINDIR)/SubMiner.AppImage"; \
|
||||
else \
|
||||
@@ -211,6 +223,8 @@ install-macos: build-launcher
|
||||
@install -m 0755 "$(LAUNCHER_OUT)" "$(BINDIR)/$(APP_NAME)"
|
||||
@install -d "$(MACOS_DATA_DIR)/themes"
|
||||
@install -m 0644 "./$(THEME_SOURCE)" "$(MACOS_DATA_DIR)/themes/$(THEME_FILE)"
|
||||
@install -d "$(MACOS_DATA_DIR)/plugin/subminer"
|
||||
@cp -R ./plugin/subminer/. "$(MACOS_DATA_DIR)/plugin/subminer/"
|
||||
@install -d "$(MACOS_APP_DIR)"
|
||||
@if [ -n "$(MACOS_APP_SRC)" ]; then \
|
||||
rm -rf "$(MACOS_APP_DEST)"; \
|
||||
@@ -226,26 +240,33 @@ install-macos: build-launcher
|
||||
fi
|
||||
@printf '%s\n' "Installed to:" " $(BINDIR)/subminer" " $(MACOS_DATA_DIR)/themes/$(THEME_FILE)" " $(MACOS_APP_DEST)"
|
||||
|
||||
install-plugin:
|
||||
@printf '%s\n' "[INFO] Installing mpv plugin artifacts"
|
||||
@install -d "$(MPV_SCRIPTS_DIR)"
|
||||
@rm -f "$(MPV_SCRIPTS_DIR)/subminer.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"
|
||||
@printf '%s\n' "Installed to:" " $(MPV_SCRIPTS_DIR)/subminer/main.lua" " $(MPV_SCRIPTS_DIR)/subminer/" " $(MPV_SCRIPT_OPTS_DIR)/subminer.conf"
|
||||
install-windows:
|
||||
@printf '%s\n' "[INFO] Windows builds run via: bun run build:win"
|
||||
@printf '%s\n' "[INFO] SubMiner-managed mpv launches inject the bundled runtime plugin; no global mpv plugin install is needed."
|
||||
|
||||
# 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"
|
||||
@rm -f "$(LINUX_DATA_DIR)/themes/$(THEME_FILE)"
|
||||
@printf '%s\n' "Removed:" " $(BINDIR)/subminer" " $(BINDIR)/SubMiner.AppImage" " $(LINUX_DATA_DIR)/themes/$(THEME_FILE)"
|
||||
@rm -rf "$(LINUX_DATA_DIR)/plugin/subminer"
|
||||
@printf '%s\n' "Removed:" " $(BINDIR)/subminer" " $(BINDIR)/SubMiner.AppImage" " $(LINUX_DATA_DIR)/themes/$(THEME_FILE)" " $(LINUX_DATA_DIR)/plugin/subminer"
|
||||
|
||||
uninstall-macos:
|
||||
@rm -f "$(BINDIR)/subminer"
|
||||
@rm -f "$(MACOS_DATA_DIR)/themes/$(THEME_FILE)"
|
||||
@rm -rf "$(MACOS_DATA_DIR)/plugin/subminer"
|
||||
@rm -rf "$(MACOS_APP_DEST)"
|
||||
@printf '%s\n' "Removed:" " $(BINDIR)/subminer" " $(MACOS_DATA_DIR)/themes/$(THEME_FILE)" " $(MACOS_APP_DEST)"
|
||||
@printf '%s\n' "Removed:" " $(BINDIR)/subminer" " $(MACOS_DATA_DIR)/themes/$(THEME_FILE)" " $(MACOS_DATA_DIR)/plugin/subminer" " $(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"
|
||||
|
||||
@@ -1,104 +1,292 @@
|
||||
<div align="center">
|
||||
<img src="assets/SubMiner.png" width="169" alt="SubMiner logo">
|
||||
<h1>SubMiner</h1>
|
||||
<strong>Look up words, mine to Anki, and enrich cards with context — without leaving mpv.</strong>
|
||||
<br /><br />
|
||||
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
[]()
|
||||
[](https://docs.subminer.moe)
|
||||
<img src="assets/SubMiner.png" width="160" alt="SubMiner logo">
|
||||
|
||||
# SubMiner
|
||||
|
||||
Look up words with Yomitan, export to Anki in one key, track your immersion — all without leaving mpv.
|
||||
|
||||
[Installation](#quick-start) · [Requirements](#requirements) · [Usage](https://docs.subminer.moe/usage) · [Documentation](https://docs.subminer.moe)
|
||||
|
||||
[](https://github.com/ksyasuda/SubMiner/releases)
|
||||
[](https://github.com/ksyasuda/SubMiner/releases/latest)
|
||||
[](https://aur.archlinux.org/packages/subminer-bin)
|
||||
[](https://github.com/ksyasuda/SubMiner)
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
[](https://www.typescriptlang.org)
|
||||
|
||||
[](https://github.com/user-attachments/assets/89e61895-e2b7-4b47-8d50-a35afe4132b2)
|
||||
|
||||
</div>
|
||||
|
||||
<br />
|
||||
## Features
|
||||
|
||||
### Dictionary Lookups
|
||||
|
||||
Yomitan runs inside the overlay. Trigger a lookup on any word for full dictionary popups — definitions, pitch accent, frequency data — without ever leaving mpv.
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](./assets/minecard.mp4)
|
||||
|
||||
<img src="docs-site/public/screenshots/yomitan-lookup.png" width="800" alt="Yomitan dictionary popup over annotated subtitles in mpv">
|
||||
</div>
|
||||
|
||||
<br />
|
||||
<br>
|
||||
|
||||
## What it does
|
||||
### Instant Anki Mining
|
||||
|
||||
SubMiner is an Electron overlay that sits on top of mpv. It turns your video player into a full sentence-mining workstation:
|
||||
Create an Anki card with the sentence, audio clip, screenshot, and machine translation from the exact playback moment with one key press, click, or controller input.
|
||||
|
||||
- **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
|
||||
- **Hover-aware playback** — By default, hovering subtitle text pauses mpv and resumes on mouse leave (`subtitleStyle.autoPauseVideoOnHover`)
|
||||
- **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
|
||||
- **Jellyfin integration** — Remote playback setup, cast device mode, and direct playback launch
|
||||
- **AniList progress** — Track episode completion and push watching progress automatically
|
||||
<div align="center">
|
||||
<img src="docs-site/public/screenshots/one-key-mining.png" width="800" alt="Anki card created from SubMiner with sentence, audio, and screenshot">
|
||||
</div>
|
||||
|
||||
## Quick start
|
||||
<br>
|
||||
|
||||
### 1. Install
|
||||
### Reading Annotations
|
||||
|
||||
**Linux (AppImage):**
|
||||
Real-time subtitle annotations with frequency highlighting, JLPT tags, N+1 targeting, and a character name dictionary. Known words fade back; new words stand out. Grammar-only tokens render as plain text so you focus on what matters.
|
||||
|
||||
```bash
|
||||
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/SubMiner.AppImage -O ~/.local/bin/SubMiner.AppImage
|
||||
chmod +x ~/.local/bin/SubMiner.AppImage
|
||||
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer -O ~/.local/bin/subminer
|
||||
chmod +x ~/.local/bin/subminer
|
||||
<div align="center">
|
||||
<img src="docs-site/public/screenshots/annotations.png" width="800" alt="Annotated subtitles with frequency coloring, JLPT underlines, and N+1 targets">
|
||||
</div>
|
||||
|
||||
```
|
||||
<br>
|
||||
|
||||
> [!NOTE]
|
||||
> The `subminer` wrapper uses a [Bun](https://bun.sh) shebang. Make sure `bun` is on your `PATH`.
|
||||
### Immersion Dashboard
|
||||
|
||||
**From source** or **macOS** — see the [installation guide](https://docs.subminer.moe/installation#from-source).
|
||||
Local stats dashboard — watch time, anime library, vocabulary growth, mining throughput, session history, and trends. All stored locally, no third-party tracking.
|
||||
|
||||
### 2. Install the mpv plugin and configuration file
|
||||
<div align="center">
|
||||
<img src="docs-site/public/screenshots/stats-overview.png" width="800" alt="Stats dashboard showing watch time, cards mined, streaks, and tracking data">
|
||||
</div>
|
||||
|
||||
```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
|
||||
```
|
||||
<br>
|
||||
|
||||
### 3. Set up Yomitan Dictionaries
|
||||
### Playlist Browser
|
||||
|
||||
```bash
|
||||
subminer app --yomitan
|
||||
```
|
||||
Browse sibling episode files and the active mpv queue in one overlay modal. Open it with `Ctrl+Alt+P` to append episodes from the current directory, jump to queued items, remove entries, or reorder the playlist without leaving playback.
|
||||
|
||||
### 4. Mine
|
||||
<div align="center">
|
||||
<img src="docs-site/public/screenshots/playlist-browser.png" width="800" alt="Stats dashboard showing watch time, cards mined, streaks, and tracking data">
|
||||
</div>
|
||||
|
||||
```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
|
||||
```
|
||||
<br>
|
||||
|
||||
### Integrations
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td><b>YouTube</b></td>
|
||||
<td>Auto-loaded yt-dlp subtitle tracks at startup with config-driven primary/secondary language priorities and a manual overlay picker on demand (<code>Ctrl+Alt+C</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>AniList</b></td>
|
||||
<td>Automatic episode tracking and progress sync</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Jellyfin</b></td>
|
||||
<td>Browse, launch, and cast media from your Jellyfin server with setup and discovery controls in the app tray</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Jimaku</b></td>
|
||||
<td>Search and download Japanese subtitles</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>alass / ffsubsync</b></td>
|
||||
<td>Automatic subtitle retiming — requires <code>alass</code> or <code>ffsubsync</code> on your <code>PATH</code> (optional; subtitle syncing is disabled without them)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>WebSocket</b></td>
|
||||
<td>Annotated subtitle feed for external clients (texthooker pages, custom tools)</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div align="center">
|
||||
<img src="docs-site/public/screenshots/texthooker.png" width="800" alt="Texthooker page receiving annotated subtitle lines via WebSocket">
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
---
|
||||
|
||||
## Requirements
|
||||
|
||||
| Required | Optional |
|
||||
| ------------------------------------------ | -------------------------------------------------- |
|
||||
| `bun` | |
|
||||
| `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 | |
|
||||
| | Required | Recommended | Optional |
|
||||
| -------------- | --------------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
|
||||
| **Player** | [`mpv`](https://mpv.io) with IPC socket | — | — |
|
||||
| **Processing** | — | `ffmpeg` (audio clips & screenshots) | `mecab` + `mecab-ipadic` (annotation POS filtering), `guessit` (AniSkip), `alass` / `ffsubsync` (subtitle sync) |
|
||||
| **Media** | — | — | `yt-dlp`, `chafa`, `ffmpegthumbnailer` |
|
||||
| **Selection** | — | — | `fzf` / `rofi` |
|
||||
|
||||
> [!TIP]
|
||||
> `ffmpeg` is not strictly required to run SubMiner, but without it audio clips and screenshots will not be attached to Anki cards. Most users will want it installed.
|
||||
|
||||
> [!NOTE]
|
||||
> [`bun`](https://bun.sh) is required if building from source or using the CLI wrapper: `subminer`. Pre-built releases (AppImage, DMG, installer) do not require it.
|
||||
|
||||
**Platform-specific:**
|
||||
|
||||
| Linux | macOS | Windows |
|
||||
| ------------------------------------------------------------ | ------------------------ | ------------- |
|
||||
| Hyprland (`hyprctl`) · X11/Xwayland (`xdotool` + `xwininfo`) | Accessibility permission | No extra deps |
|
||||
|
||||
> [!NOTE]
|
||||
> **Wayland support is compositor-specific.** Wayland has no universal API for window positioning and each compositor exposes its own IPC, so SubMiner needs a dedicated backend per compositor. Hyprland is the only native Wayland backend supported currenlty. All other Linux compositors require both mpv and SubMiner to run under X11 or Xwayland. The launcher detects your compositor and configures this automatically.
|
||||
|
||||
<details>
|
||||
<summary><b>Arch Linux</b></summary>
|
||||
|
||||
```bash
|
||||
paru -S --needed mpv ffmpeg
|
||||
# Optional
|
||||
paru -S --needed mecab-git mecab-ipadic yt-dlp fzf rofi chafa ffmpegthumbnailer xdotool xorg-xwininfo
|
||||
# Optional: subtitle sync (install at least one for subtitle syncing to work)
|
||||
paru -S --needed alass python-ffsubsync
|
||||
# X11 / Xwayland (required for non-Hyprland compositors)
|
||||
paru -S --needed xdotool xorg-xwininfo
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS</b></summary>
|
||||
|
||||
```bash
|
||||
brew install mpv ffmpeg
|
||||
# Optional
|
||||
brew install mecab mecab-ipadic yt-dlp fzf rofi chafa ffmpegthumbnailer
|
||||
# Optional: subtitle sync (install at least one for subtitle syncing to work)
|
||||
brew install alass
|
||||
pip install ffsubsync
|
||||
```
|
||||
|
||||
Grant Accessibility permission to SubMiner in **System Settings > Privacy & Security > Accessibility**.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Windows</b></summary>
|
||||
|
||||
Install [`mpv`](https://mpv.io/installation/) and [`ffmpeg`](https://ffmpeg.org/download.html) and ensure both are on your `PATH`.
|
||||
|
||||
Optionally install [MeCab for Windows](https://taku910.github.io/mecab/#download) with the UTF-8 dictionary for additional metadata enrichment.
|
||||
|
||||
</details>
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Install
|
||||
|
||||
<details>
|
||||
<summary><b>Arch Linux (AUR)</b></summary>
|
||||
|
||||
```bash
|
||||
paru -S subminer-bin
|
||||
```
|
||||
|
||||
Or manually:
|
||||
|
||||
```bash
|
||||
git clone https://aur.archlinux.org/subminer-bin.git && cd subminer-bin && makepkg -si
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Linux (AppImage)</b></summary>
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.local/bin
|
||||
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/SubMiner.AppImage -O ~/.local/bin/SubMiner.AppImage \
|
||||
&& chmod +x ~/.local/bin/SubMiner.AppImage
|
||||
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer -O ~/.local/bin/subminer \
|
||||
&& chmod +x ~/.local/bin/subminer
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> The `subminer` wrapper uses a [Bun](https://bun.sh) shebang. First-run setup can optionally install Bun and the launcher into an existing writable PATH directory.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>macOS</b></summary>
|
||||
|
||||
Download the latest DMG or ZIP from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest) and drag `SubMiner.app` into `/Applications`.
|
||||
|
||||
Also download the `subminer` launcher (recommended):
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.local/bin
|
||||
curl -fSL https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer -o ~/.local/bin/subminer \
|
||||
&& chmod +x ~/.local/bin/subminer
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> The `subminer` launcher uses a [Bun](https://bun.sh) shebang. First-run setup can optionally install Bun and the launcher into an existing writable PATH directory. Make sure `~/.local/bin` is on your PATH before installing there.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>Windows</b></summary>
|
||||
|
||||
Download the latest installer (`.exe`) [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest). Make sure `mpv` is on your `PATH`.
|
||||
|
||||
**Note:** On Windows the recommended way to run playback is with the **SubMiner mpv** shortcut created during first-run setup. First-run setup can also optionally install Bun and a `subminer.cmd` command shim to your user PATH, so new terminals can run `subminer` without adding `SubMiner.exe` to PATH.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><b>From source</b></summary>
|
||||
|
||||
See the [build-from-source guide](https://docs.subminer.moe/installation#from-source).
|
||||
|
||||
</details>
|
||||
|
||||
### 2. First Launch
|
||||
|
||||
```bash
|
||||
subminer app --setup # launch the first-run setup wizard
|
||||
```
|
||||
|
||||
SubMiner creates a default config, starts in the system tray, and opens a setup popup that walks you through installing Yomitan dictionaries. The setup popup can also optionally install Bun and the `subminer` command-line launcher; those choices do not block setup completion.
|
||||
|
||||
> [!NOTE]
|
||||
> On Windows, run `SubMiner.exe` directly — it opens the setup wizard automatically on first launch.
|
||||
|
||||
### 3. Mine
|
||||
|
||||
```bash
|
||||
subminer video.mkv # play video with overlay
|
||||
subminer --start video.mkv # explicit overlay start
|
||||
subminer stats # open immersion dashboard
|
||||
subminer stats -b # stats daemon in background
|
||||
subminer stats -s # stop background stats daemon
|
||||
```
|
||||
|
||||
On **Windows**, use the **SubMiner mpv** shortcut created during first-run setup — double-click it to open mpv, or drag a video file onto it. You can also run `SubMiner.exe --launch-mpv` from a terminal.
|
||||
|
||||
## Documentation
|
||||
|
||||
For full guides on configuration, Anki, Jellyfin, and more, see [docs.subminer.moe](https://docs.subminer.moe).
|
||||
Full guides on configuration, Anki setup, Jellyfin, immersion tracking, and more: **[docs.subminer.moe](https://docs.subminer.moe)**
|
||||
|
||||
---
|
||||
|
||||
## 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).
|
||||
SubMiner builds on the work of these open-source projects:
|
||||
|
||||
| Project | Role |
|
||||
| ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------- |
|
||||
| [Anacreon-Script](https://github.com/friedrich-de/Anacreon-Script) | Inspiration for the mining workflow |
|
||||
| [asbplayer](https://github.com/killergerbah/asbplayer) | Inspiration for subtitle sidebar and logic for YouTube subtitle parsing |
|
||||
| [Bee's Character Dictionary](https://github.com/bee-san/Japanese_Character_Name_Dictionary) | Character name recognition in subtitles |
|
||||
| [GameSentenceMiner](https://github.com/bpwhelan/GameSentenceMiner) | Inspiration for Electron overlay with Yomitan integration |
|
||||
| [jellyfin-mpv-shim](https://github.com/jellyfin/jellyfin-mpv-shim) | Jellyfin integration |
|
||||
| [Jimaku.cc](https://jimaku.cc) | Japanese subtitle search and downloads |
|
||||
| [Renji's Texthooker Page](https://github.com/Renji-XD/texthooker-ui) | Base for the WebSocket texthooker integration |
|
||||
| [Yomitan](https://github.com/yomidevs/yomitan) | Dictionary engine powering all lookups and the morphological parser |
|
||||
| [yomitan-jlpt-vocab](https://github.com/stephenmk/yomitan-jlpt-vocab) | JLPT level tags for vocabulary |
|
||||
|
||||
## License
|
||||
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.resolveMacAppBundlePath = resolveMacAppBundlePath;
|
||||
exports.isMacApplicationsFolderBundle = isMacApplicationsFolderBundle;
|
||||
exports.isKnownLinuxPackageManagedAppImage = isKnownLinuxPackageManagedAppImage;
|
||||
exports.isNativeUpdaterSupported = isNativeUpdaterSupported;
|
||||
exports.configureAutoUpdater = configureAutoUpdater;
|
||||
exports.createElectronAppUpdater = createElectronAppUpdater;
|
||||
const node_fs_1 = require("node:fs");
|
||||
const node_child_process_1 = require("node:child_process");
|
||||
const node_os_1 = __importDefault(require("node:os"));
|
||||
const node_path_1 = __importDefault(require("node:path"));
|
||||
const node_util_1 = require("node:util");
|
||||
const electron_updater_1 = require("electron-updater");
|
||||
const release_assets_1 = require("./release-assets");
|
||||
const updaterErrorListeners = new WeakMap();
|
||||
const execFileAsync = (0, node_util_1.promisify)(node_child_process_1.execFile);
|
||||
function resolveMacAppBundlePath(execPath) {
|
||||
const marker = '.app/Contents/MacOS/';
|
||||
const markerIndex = execPath.indexOf(marker);
|
||||
if (markerIndex < 0)
|
||||
return null;
|
||||
return execPath.slice(0, markerIndex + '.app'.length);
|
||||
}
|
||||
async function readMacCodeSignature(appBundlePath) {
|
||||
try {
|
||||
const result = await execFileAsync('/usr/bin/codesign', ['-dv', '--verbose=4', appBundlePath], {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
return `${result.stdout ?? ''}\n${result.stderr ?? ''}`;
|
||||
}
|
||||
catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function realpathOrOriginal(filePath) {
|
||||
try {
|
||||
return (0, node_fs_1.realpathSync)(filePath);
|
||||
}
|
||||
catch {
|
||||
return filePath;
|
||||
}
|
||||
}
|
||||
function isSameOrInsideDirectory(parentPath, candidatePath) {
|
||||
const relative = node_path_1.default.relative(parentPath, candidatePath);
|
||||
return (relative === '' ||
|
||||
(relative.length > 0 && !relative.startsWith('..') && !node_path_1.default.isAbsolute(relative)));
|
||||
}
|
||||
function isMacApplicationsFolderBundle(appBundlePath, homeDir = node_os_1.default.homedir()) {
|
||||
const resolvedBundlePath = node_path_1.default.resolve(appBundlePath);
|
||||
return (isSameOrInsideDirectory('/Applications', resolvedBundlePath) ||
|
||||
isSameOrInsideDirectory(node_path_1.default.join(homeDir, 'Applications'), resolvedBundlePath));
|
||||
}
|
||||
function isKnownLinuxPackageManagedAppImage(appImagePath) {
|
||||
return realpathOrOriginal(appImagePath) === '/opt/SubMiner/SubMiner.AppImage';
|
||||
}
|
||||
async function isNativeUpdaterSupported(options) {
|
||||
if (!options.isPackaged) {
|
||||
options.log?.('Skipping native updater because this build is not packaged.');
|
||||
return false;
|
||||
}
|
||||
if (options.platform === 'linux') {
|
||||
options.log?.('Skipping native Linux updater because Linux tray checks use GitHub release assets.');
|
||||
return false;
|
||||
}
|
||||
if (options.platform !== 'darwin') {
|
||||
options.log?.('Skipping native updater because this platform uses GitHub metadata checks.');
|
||||
return false;
|
||||
}
|
||||
const appBundlePath = resolveMacAppBundlePath(options.execPath);
|
||||
if (!appBundlePath) {
|
||||
options.log?.('Skipping native macOS updater because the app bundle path could not be resolved.');
|
||||
return false;
|
||||
}
|
||||
if (!isMacApplicationsFolderBundle(appBundlePath, options.homeDir)) {
|
||||
options.log?.('Skipping native macOS updater because the app is not installed in an Applications folder.');
|
||||
return false;
|
||||
}
|
||||
const signature = await (options.readCodeSignature ?? readMacCodeSignature)(appBundlePath);
|
||||
if (!signature) {
|
||||
options.log?.('Skipping native macOS updater because the app code signature could not be read.');
|
||||
return false;
|
||||
}
|
||||
if (/Signature=adhoc\b/.test(signature) || /TeamIdentifier=not set\b/.test(signature)) {
|
||||
options.log?.('Skipping native macOS updater because this build is ad-hoc signed.');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function configureAutoUpdater(updater, log = () => { }, channel = 'stable') {
|
||||
updater.autoDownload = false;
|
||||
// On macOS this avoids invoking Squirrel until the explicit restart/install step.
|
||||
updater.autoInstallOnAppQuit = false;
|
||||
updater.allowPrerelease = channel === 'prerelease';
|
||||
updater.allowDowngrade = false;
|
||||
updater.logger = {
|
||||
info: () => { },
|
||||
debug: () => { },
|
||||
warn: (message) => log(message),
|
||||
error: (message) => log(message),
|
||||
};
|
||||
const previousErrorListener = updaterErrorListeners.get(updater);
|
||||
if (previousErrorListener) {
|
||||
if (updater.off) {
|
||||
updater.off('error', previousErrorListener);
|
||||
}
|
||||
else {
|
||||
updater.removeListener?.('error', previousErrorListener);
|
||||
}
|
||||
}
|
||||
if (updater.on) {
|
||||
const errorListener = (error) => {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
log(`Updater error event: ${message}`);
|
||||
};
|
||||
updater.on('error', errorListener);
|
||||
updaterErrorListeners.set(updater, errorListener);
|
||||
}
|
||||
return updater;
|
||||
}
|
||||
function createElectronAppUpdater(options) {
|
||||
const getChannel = options.getChannel ?? (() => 'stable');
|
||||
const updater = configureAutoUpdater(options.updater ?? electron_updater_1.autoUpdater, options.log, getChannel());
|
||||
if (options.configureHttpExecutor) {
|
||||
// electron-updater has no public executor hook; keep the macOS cURL override localized.
|
||||
updater.httpExecutor = options.configureHttpExecutor();
|
||||
}
|
||||
if (options.disableDifferentialDownload !== undefined) {
|
||||
updater.disableDifferentialDownload = options.disableDifferentialDownload;
|
||||
}
|
||||
let nativeUpdaterSupported = null;
|
||||
async function getNativeUpdaterSupported() {
|
||||
if (!options.isNativeUpdaterSupported)
|
||||
return true;
|
||||
if (nativeUpdaterSupported === null) {
|
||||
nativeUpdaterSupported = Promise.resolve(options.isNativeUpdaterSupported());
|
||||
}
|
||||
return nativeUpdaterSupported;
|
||||
}
|
||||
return {
|
||||
async checkForUpdates(channel) {
|
||||
if (!options.isPackaged) {
|
||||
return {
|
||||
available: false,
|
||||
version: options.currentVersion,
|
||||
canUpdate: false,
|
||||
};
|
||||
}
|
||||
if (!(await getNativeUpdaterSupported())) {
|
||||
options.log('Skipping native app update check because native updater is unsupported.');
|
||||
return {
|
||||
available: false,
|
||||
version: options.currentVersion,
|
||||
canUpdate: false,
|
||||
};
|
||||
}
|
||||
configureAutoUpdater(updater, options.log, channel ?? getChannel());
|
||||
const result = await updater.checkForUpdates();
|
||||
const version = result?.updateInfo?.version ?? options.currentVersion;
|
||||
return {
|
||||
available: (0, release_assets_1.compareSemverLike)(version, options.currentVersion) > 0,
|
||||
version,
|
||||
canUpdate: true,
|
||||
};
|
||||
},
|
||||
async downloadUpdate() {
|
||||
if (!options.isPackaged) {
|
||||
options.log('Skipping app update download because this build is not packaged.');
|
||||
return;
|
||||
}
|
||||
if (!(await getNativeUpdaterSupported())) {
|
||||
options.log('Skipping app update download because native updater is unsupported.');
|
||||
return;
|
||||
}
|
||||
await updater.downloadUpdate();
|
||||
},
|
||||
async quitAndInstall() {
|
||||
if (!options.isPackaged) {
|
||||
options.log('Skipping app update install because this build is not packaged.');
|
||||
return;
|
||||
}
|
||||
if (!(await getNativeUpdaterSupported())) {
|
||||
options.log('Skipping app update install because native updater is unsupported.');
|
||||
return;
|
||||
}
|
||||
updater.quitAndInstall(false, true);
|
||||
},
|
||||
};
|
||||
}
|
||||
//# sourceMappingURL=app-updater.js.map
|
||||
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 141 KiB |
|
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 21 MiB After Width: | Height: | Size: 3.0 MiB |
@@ -1,16 +0,0 @@
|
||||
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'
|
||||
auto_open_browser: false
|
||||
default_port: 6420
|
||||
remote_operations: true
|
||||
auto_commit: false
|
||||
bypass_git_hooks: false
|
||||
check_active_branches: true
|
||||
active_branch_days: 30
|
||||
task_prefix: 'task'
|
||||
@@ -0,0 +1,15 @@
|
||||
# Config Settings Window
|
||||
|
||||
Status: draft
|
||||
Owner: Kyle Yasuda
|
||||
Created: 2026-05-17
|
||||
|
||||
## Goal
|
||||
|
||||
Add a dedicated configuration window that groups settings by user workflow while saving back to the existing `config.jsonc` paths.
|
||||
|
||||
## Notes
|
||||
|
||||
- Full current config surface, excluding legacy/ignored compatibility keys.
|
||||
- Preserve JSONC comments/formatting when saving.
|
||||
- Surface hot-reload vs restart-required results.
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
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.
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
id: TASK-70
|
||||
title: >-
|
||||
Overlay runtime refactor: remove invisible mode and bind visible overlay to
|
||||
mpv subtitles
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-28 02:38'
|
||||
updated_date: '2026-02-28 22:36'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'commit:a14c9da'
|
||||
- 'commit:74554a3'
|
||||
- 'commit:75442a4'
|
||||
- 'commit:dde51f8'
|
||||
- 'commit:9e4e588'
|
||||
- src/main/overlay-runtime.ts
|
||||
- src/main/runtime/overlay-mpv-sub-visibility.ts
|
||||
- src/renderer/renderer.ts
|
||||
- docs/plans/2026-02-26-secondary-subtitles-main-overlay.md
|
||||
priority: medium
|
||||
ordinal: 1000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Scope: Branch-only commits main..HEAD on refactor-overlay (a14c9da through 9e4e588) rebuilt overlay behavior around visible overlay mode and removed legacy invisible overlay paths.
|
||||
|
||||
Delivered behavior:
|
||||
|
||||
- Removed renderer invisible overlay layout/offset helpers and main hover-highlight runtime code paths.
|
||||
- Added explicit overlay-to-mpv subtitle visibility synchronization so visible overlay state controls primary subtitle visibility consistently.
|
||||
- Hardened overlay runtime/bootstrap lifecycle around modal fallback open state and bridge send path edge cases.
|
||||
- Updated plugin/config/docs defaults to reflect visible-overlay-first behavior and subtitle binding controls.
|
||||
|
||||
Risk/impact context:
|
||||
|
||||
- Large cross-layer refactor touching runtime wiring, renderer event handling, and plugin behavior.
|
||||
- Regression coverage added/updated for overlay runtime, mpv protocol handling, renderer cleanup, and subtitle rendering paths.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Completed and validated in branch commit set before merge. Refactor reduces dead overlay modes, centralizes subtitle visibility behavior, and documents new defaults/constraints.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
id: TASK-71
|
||||
title: >-
|
||||
Anki integration: add local AnkiConnect proxy transport for push-based
|
||||
auto-enrichment
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-28 02:38'
|
||||
updated_date: '2026-03-04 13:55'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- src/anki-integration/anki-connect-proxy.ts
|
||||
- src/anki-integration/anki-connect-proxy.test.ts
|
||||
- src/anki-integration.ts
|
||||
- src/config/resolve/anki-connect.ts
|
||||
- src/core/services/tokenizer/yomitan-parser-runtime.ts
|
||||
- src/core/services/tokenizer/yomitan-parser-runtime.test.ts
|
||||
- docs/anki-integration.md
|
||||
- config.example.jsonc
|
||||
priority: medium
|
||||
ordinal: 2000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Scope: Current unmerged working-tree changes implement an optional local AnkiConnect-compatible proxy and transport switching for card enrichment.
|
||||
|
||||
Delivered behavior:
|
||||
|
||||
- Added proxy server that forwards AnkiConnect requests and enqueues addNote/addNotes note IDs for post-create enrichment, with de-duplication and loop-configuration protection.
|
||||
- Added follow-up response-shape compatibility handling so proxy enqueue works for both envelope (`{result,error}`) and bare JSON payloads, including `multi` variants.
|
||||
- Added config schema/defaults/resolution for ankiConnect.proxy (enabled, host, port, upstreamUrl) with validation warnings and fallback behavior.
|
||||
- Runtime now supports transport switching (polling vs proxy) and restarts transport when runtime config patches change transport keys.
|
||||
- Added Yomitan default-profile server sync helper to keep bundled parser profile aligned with configured Anki endpoint.
|
||||
- Updated user docs/config examples for proxy mode setup, troubleshooting, and mining workflow behavior.
|
||||
|
||||
Risk/impact context:
|
||||
|
||||
- New network surface on local host/port; correctness depends on safe proxy upstream configuration and robust response handling.
|
||||
- Tests added for proxy queue behavior, config resolution, and parser sync routines.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
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 -->
|
||||
@@ -1,42 +0,0 @@
|
||||
---
|
||||
id: TASK-72
|
||||
title: 'macOS config validation UX: show full warning details in native dialog'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-28 02:38'
|
||||
updated_date: '2026-02-28 22:36'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- 'commit:cc2f9ef'
|
||||
- src/main/config-validation.ts
|
||||
- src/main/runtime/startup-config.ts
|
||||
- docs/configuration.md
|
||||
priority: low
|
||||
ordinal: 3000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Scope: Commit cc2f9ef improves startup config-warning visibility on macOS by ensuring full details are surfaced in the native UI path and reflected in docs.
|
||||
|
||||
Delivered behavior:
|
||||
|
||||
- Config validation/runtime wiring updated so macOS users can access complete warning details instead of truncated notification-only text.
|
||||
- Added/updated tests around config validation and startup config warning flows.
|
||||
- Updated configuration docs to clarify platform-specific warning presentation behavior.
|
||||
|
||||
Risk/impact context:
|
||||
|
||||
- Low runtime risk; primarily user-facing diagnostics clarity improvement.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Completed small follow-up fix to reduce config-debug friction on macOS.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,84 +0,0 @@
|
||||
---
|
||||
id: TASK-73
|
||||
title: 'MPV plugin: split into modules and optimize startup/command runtime'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-28 20:50'
|
||||
updated_date: '2026-02-28 22:36'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- plugin/subminer/main.lua
|
||||
- plugin/subminer/bootstrap.lua
|
||||
- plugin/subminer/process.lua
|
||||
- plugin/subminer/aniskip.lua
|
||||
- plugin/subminer/environment.lua
|
||||
- plugin/subminer/lifecycle.lua
|
||||
- plugin/subminer/messages.lua
|
||||
- plugin/subminer/ui.lua
|
||||
- plugin/subminer/hover.lua
|
||||
- plugin/subminer/options.lua
|
||||
- plugin/subminer/state.lua
|
||||
- plugin/subminer.conf
|
||||
- scripts/test-plugin-start-gate.lua
|
||||
- scripts/test-plugin-process-start-retries.lua
|
||||
- launcher/commands/playback-command.ts
|
||||
- launcher/mpv.ts
|
||||
- launcher/mpv.test.ts
|
||||
- launcher/smoke.e2e.test.ts
|
||||
- Makefile
|
||||
- package.json
|
||||
- docs/mpv-plugin.md
|
||||
- docs/installation.md
|
||||
- docs/architecture.md
|
||||
- README.md
|
||||
priority: medium
|
||||
ordinal: 4000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Scope: Replace monolithic `plugin/subminer.lua` with modular plugin runtime; optimize command execution paths; align install/docs/tests; fix launcher smoke instability.
|
||||
|
||||
Delivered behavior:
|
||||
|
||||
- Full plugin cutover to `plugin/subminer/main.lua` + module directory (no runtime compatibility shim with old monolith file).
|
||||
- Process/control command path moved toward async subprocess usage for non-start actions (`stop`, `toggle`, `settings`, restart stop leg), reducing synchronous blocking in mpv script runtime.
|
||||
- AniSkip path guarded: lookup runs only in SubMiner context (launcher metadata, explicit script-message refresh, or detected running app), instead of every opened file.
|
||||
- AniSkip lookup pipeline moved to async subprocess calls (no sync `ps`/`curl` on `file-loaded`) with deferred fetch after auto-start and session-level MAL/title/payload caching.
|
||||
- Startup/runtime loading updated with lazy module initialization via bootstrap proxies.
|
||||
- Plugin install flow updated to copy `plugin/subminer/` directory and remove legacy `~/.config/mpv/scripts/subminer.lua` file.
|
||||
- Added plugin gate script wiring to package scripts (`test:plugin:src`) and launcher test flow.
|
||||
- Smoke tests stabilized across sandbox environments where UNIX socket bind can return `EPERM` while preserving normal-path assertions.
|
||||
- Playback command cleanup race fixed when mpv exits before exit-listener registration.
|
||||
|
||||
Risk/impact context:
|
||||
|
||||
- mpv plugin loading path changed from single-file to module directory; packaging/install paths must stay consistent with release assets.
|
||||
- Async control/AniSkip path changes reduce blocking but can surface timing differences; regression checks added for cold start, file-load gating, and explicit refresh behavior.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
AniSkip gate/async update delivered in plugin runtime:
|
||||
|
||||
- `plugin/subminer/lifecycle.lua`: deferred AniSkip fetch and overlay-start trigger.
|
||||
- `plugin/subminer/aniskip.lua`: async lookup pipeline + context guard + session caches.
|
||||
- `plugin/subminer/environment.lua`: async app-running detection with short cache.
|
||||
- `plugin/subminer/messages.lua`: explicit script-message trigger wiring.
|
||||
|
||||
Regression coverage updated:
|
||||
|
||||
- `scripts/test-plugin-start-gate.lua` now verifies:
|
||||
- no sync `ps`/`curl` on non-context file load
|
||||
- no AniSkip network lookup on non-context file load
|
||||
- script-message refresh forces async AniSkip lookup
|
||||
|
||||
Validation run:
|
||||
|
||||
- `bun run test:plugin:src` pass.
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,76 +0,0 @@
|
||||
---
|
||||
id: TASK-74
|
||||
title: 'Startup warmups: configurable warmup vs defer with low-power mode'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-27 21:05'
|
||||
updated_date: '2026-03-01 04:14'
|
||||
labels: []
|
||||
dependencies: []
|
||||
references:
|
||||
- src/types.ts
|
||||
- src/config/definitions/defaults-core.ts
|
||||
- src/config/definitions/options-core.ts
|
||||
- src/config/definitions/template-sections.ts
|
||||
- src/config/resolve/core-domains.ts
|
||||
- src/main/runtime/startup-warmups.ts
|
||||
- src/main/runtime/startup-warmups-main-deps.ts
|
||||
- src/main/runtime/composers/mpv-runtime-composer.ts
|
||||
- src/core/services/startup.ts
|
||||
- src/main.ts
|
||||
- src/config/config.test.ts
|
||||
- src/main/runtime/startup-warmups.test.ts
|
||||
- src/main/runtime/startup-warmups-main-deps.test.ts
|
||||
- src/core/services/app-ready.test.ts
|
||||
priority: medium
|
||||
ordinal: 7000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Add startup warmup controls to allow per-integration warmup or deferred first-use loading.
|
||||
|
||||
Scope:
|
||||
|
||||
- New config section `startupWarmups` with toggles for `mecab`, `yomitanExtension`, `subtitleDictionaries`, and `jellyfinRemoteSession`.
|
||||
- New `startupWarmups.lowPowerMode` policy: defer everything except Yomitan extension.
|
||||
- Keep default behavior as full warmup.
|
||||
- Ensure deferred integrations lazy-load on first real usage path.
|
||||
- Add test coverage for config parsing/defaults and warmup scheduling behavior.
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Implemented:
|
||||
|
||||
- Added `startupWarmups` to config types/defaults/options/template/resolve.
|
||||
- Warmup scheduler now uses per-integration gating functions.
|
||||
- Low-power mode now defers MeCab, subtitle dictionaries, and Jellyfin remote session warmups while still warming Yomitan extension.
|
||||
- Tokenization path guarantees lazy first-use init for deferred dependencies (Yomitan extension, MeCab when missing, subtitle dictionaries).
|
||||
- Added/updated tests across config and runtime warmup modules.
|
||||
|
||||
Validation:
|
||||
|
||||
- `bun run test:config:src`
|
||||
- `bun run test:core:src`
|
||||
- `tsc --noEmit`
|
||||
|
||||
Follow-up updates:
|
||||
|
||||
- Startup now triggers warmups earlier in app-ready flow (right after config validation/log-level setup) instead of waiting for initial args/overlay actions. Goal: tokenization warmup is already done or mostly done by first visible-subs toggle.
|
||||
- Tokenization warmup scheduling consolidated as `subtitle-tokenization` stage; when enabled by toggles, it runs Yomitan extension first, then MeCab/dictionary warmups.
|
||||
- Added per-stage debug logs for warmup progress and skip reasons:
|
||||
- `stage start/ready: yomitan-extension`
|
||||
- `stage start/ready: mecab`
|
||||
- `stage start/ready: subtitle-dictionaries`
|
||||
- `stage start/ready: jellyfin-remote-session`
|
||||
- `stage skipped: jellyfin-remote-session (disabled|auto-connect off)`
|
||||
- Added regression tests for stage-level logging and earlier startup ordering:
|
||||
- `src/main/runtime/startup-warmups.test.ts`
|
||||
- `src/main/runtime/startup-warmups-main-deps.test.ts`
|
||||
- `src/core/services/app-ready.test.ts`
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
id: TASK-75
|
||||
title: 'Tokenizer: configurable POS exclusions for N+1 and frequency annotations'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-01 01:23'
|
||||
updated_date: '2026-03-01 04:14'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 6000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
N+1 and frequency highlighting should ignore non-learning tokens (e.g., particles/auxiliary forms) based on MeCab POS1 tags, while remaining user-configurable.
|
||||
|
||||
Problem example: for subtitle phrase containing になれば, the highlighted N+1 target should not be the non-useful inflection/token piece when POS indicates an excluded class.
|
||||
|
||||
Implement configurable exclusion defaults with add/remove overrides so users can tune behavior without code changes.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Default exclusion set omits non-useful POS1 classes from both N+1 candidate selection and frequency highlighting.
|
||||
- [x] #2 Users can add extra POS1 exclusions and remove defaults via config.
|
||||
- [x] #3 Tokenizer/annotation tests cover default behavior and config add/remove overrides.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Implemented configurable annotation POS exclusions with defaults+add/remove for both MeCab POS1 and POS2, wired to N+1 candidate selection and frequency highlighting. Added POS2 default exclusion (非自立), expanded POS1 defaults for function words, added Yomitan->MeCab enrichment to carry pos2/pos3 metadata, updated config docs/examples, and added regression tests including になれば case.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,39 +0,0 @@
|
||||
---
|
||||
id: TASK-76
|
||||
title: 'Tokenizer: remove POS exclusion config surface and keep hardcoded defaults'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-01 02:45'
|
||||
updated_date: '2026-03-01 04:14'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 5000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
Remove user-facing config keys for annotation POS exclusions. Keep N+1/frequency POS exclusion behavior as built-in defaults with no config required.
|
||||
|
||||
Scope: remove config parsing/registry/docs/example for annotationFilters.pos1Exclusions/pos2Exclusions while preserving runtime filtering behavior.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 No user-facing config option exists for annotation POS exclusions.
|
||||
- [x] #2 Runtime N+1/frequency exclusion behavior remains active via built-in defaults.
|
||||
- [x] #3 Config/docs/example/tests updated accordingly.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Removed user-facing subtitleStyle.annotationFilters POS exclusion configuration (schema/resolver/options/docs/example). POS-based N+1/frequency filtering now always uses built-in defaults in runtime. Preserved robust exclusion behavior including merged-token overlap POS handling and N+1-only MeCab enrichment path.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
id: TASK-77
|
||||
title: 'Subtitle hover: auto-pause playback with config toggle'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-28 22:43'
|
||||
updated_date: '2026-03-04 12:07'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 8000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
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.
|
||||
- Docs/examples/tests updated.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 `subtitleStyle.autoPauseVideoOnHover` exists and defaults to `true`.
|
||||
- [x] #2 Overlay pauses playback on subtitle hover and resumes on leave only when hover-triggered pause occurred.
|
||||
- [x] #3 Main/renderer IPC exposes pause-state query for safe hover behavior.
|
||||
- [x] #4 Config docs/examples and user docs/readme mention the new behavior and toggle.
|
||||
- [x] #5 Regression tests cover config parsing/validation and hover behavior edge cases.
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
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 -->
|
||||
@@ -1,55 +0,0 @@
|
||||
---
|
||||
id: TASK-78
|
||||
title: 'Launcher + mpv plugin: auto-start visible overlay pause-until-ready and single-start guard'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-02-28 22:45'
|
||||
updated_date: '2026-02-28 22:45'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 9000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
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.
|
||||
- Prevent duplicate auto-start attempts from showing `SubMiner already running` OSD.
|
||||
- Keep startup/loading OSD messaging visible and update docs/tests.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Launcher reads `auto_start`, `auto_start_visible_overlay`, and `auto_start_pause_until_ready` from `subminer.conf` and starts mpv with `--pause=yes` when all are enabled.
|
||||
- [x] #2 Plugin pauses on eligible auto-start and resumes only on readiness signal or timeout fallback.
|
||||
- [x] #3 Main process emits `script-message subminer-autoplay-ready` after subtitle tokenization is ready.
|
||||
- [x] #4 Auto-start duplicate triggers are idempotent (no duplicate `--start` behavior and no spurious `Already running` OSD for auto-start path).
|
||||
- [x] #5 Docs and regression tests cover defaults, startup gating behavior, and duplicate-start suppression.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- 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.
|
||||
- Split auto-start visibility control into separate control commands and added duplicate auto-start idempotency guard to suppress repeated auto-start `Already running` noise.
|
||||
- Updated plugin defaults to enabled (`auto_start=yes`, `auto_start_visible_overlay=yes`, `auto_start_pause_until_ready=yes`) and refreshed docs (`README`, usage, launcher, installation, plugin/config docs).
|
||||
- Added/updated regression coverage (`scripts/test-plugin-start-gate.lua`, launcher smoke/unit tests) validating paused startup, readiness resume, and duplicate-start suppression.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
id: TASK-79
|
||||
title: 'Jimaku modal: auto-close after successful subtitle load'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-01 13:52'
|
||||
updated_date: '2026-03-01 14:06'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 10000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
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).
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Successful subtitle file selection/download in Jimaku closes modal automatically.
|
||||
- [x] #2 Existing error path keeps modal open and shows error.
|
||||
- [x] #3 Regression test covers success auto-close behavior.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Fixed renderer Jimaku success flow to close modal immediately after successful `jimakuDownloadFile` result. Added regression test (`src/renderer/modals/jimaku.test.ts`) that reproduces keyboard file-selection success path and asserts modal close state + `notifyOverlayModalClosed('jimaku')` emission. Kept failure path unchanged.
|
||||
|
||||
Also wired new test into `test:core:src` and `test:core:dist` package scripts.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
id: TASK-80
|
||||
title: 'Jimaku download: rename subtitle to current video basename'
|
||||
status: Done
|
||||
assignee: []
|
||||
created_date: '2026-03-01 14:17'
|
||||
updated_date: '2026-03-01 14:19'
|
||||
labels: []
|
||||
dependencies: []
|
||||
priority: medium
|
||||
ordinal: 11000
|
||||
---
|
||||
|
||||
## Description
|
||||
|
||||
<!-- SECTION:DESCRIPTION:BEGIN -->
|
||||
|
||||
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.
|
||||
|
||||
<!-- SECTION:DESCRIPTION:END -->
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
<!-- AC:BEGIN -->
|
||||
|
||||
- [x] #1 Jimaku subtitle destination name uses current media basename plus `.ja` and subtitle extension.
|
||||
- [x] #2 Existing duplicate filename conflict handling still works.
|
||||
- [x] #3 Regression tests cover renamed destination path behavior.
|
||||
|
||||
<!-- AC:END -->
|
||||
|
||||
## Final Summary
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:BEGIN -->
|
||||
|
||||
Jimaku download path generation now derives subtitle filename from currently playing media basename and keeps subtitle extension from Jimaku file (`anime.mkv` + `.srt` => `anime.ja.srt`). Added pure helper `buildJimakuSubtitleFilenameFromMediaPath` and routed IPC download flow through it before existing duplicate-path conflict handling. Added regression tests for local path, missing extension fallback, and remote URL media paths.
|
||||
|
||||
<!-- SECTION:FINAL_SUMMARY:END -->
|
||||
@@ -1,58 +0,0 @@
|
||||
---
|
||||
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 -->
|
||||
@@ -1,60 +0,0 @@
|
||||
---
|
||||
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 -->
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
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 -->
|
||||
@@ -1,37 +0,0 @@
|
||||
---
|
||||
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 -->
|
||||
@@ -1,70 +0,0 @@
|
||||
---
|
||||
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 -->
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
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 -->
|
||||
@@ -1,68 +0,0 @@
|
||||
---
|
||||
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 -->
|
||||
@@ -1,71 +0,0 @@
|
||||
---
|
||||
id: TASK-87
|
||||
title: >-
|
||||
Codebase health: harden verification and retire dead architecture identified
|
||||
in the March 2026 review
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:19'
|
||||
updated_date: '2026-03-06 03:20'
|
||||
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
|
||||
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 -->
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
id: TASK-87.1
|
||||
title: >-
|
||||
Testing workflow: make standard test commands reflect the maintained test
|
||||
surface
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:19'
|
||||
updated_date: '2026-03-06 03:21'
|
||||
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 -->
|
||||
|
||||
- [ ] #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.
|
||||
- [ ] #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.
|
||||
- [ ] #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.
|
||||
- [ ] #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. Inventory the current test surface under src/ and launcher/ and compare it to package.json scripts to classify fast, full, slow, and environment-specific suites.
|
||||
2. Replace or reduce the brittle hand-maintained allowlist so new maintained tests do not silently miss the standard matrix.
|
||||
3. Update contributor docs with the intended fast/full/environment-specific commands.
|
||||
4. Verify the new matrix by running the relevant commands and by demonstrating at least one previously omitted runtime/Anki/entry test now belongs to an automated lane.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
id: TASK-87.2
|
||||
title: >-
|
||||
Subtitle sync verification: replace the no-op subtitle lane with real
|
||||
automated coverage
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:19'
|
||||
updated_date: '2026-03-06 03:21'
|
||||
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 -->
|
||||
|
||||
- [ ] #1 The test:subtitle entrypoint runs real automated verification instead of echoing a placeholder message.
|
||||
- [ ] #2 The subtitle verification lane covers both alass and ffsubsync behavior, including at least one non-happy-path scenario relevant to current functionality.
|
||||
- [ ] #3 Contributor-facing documentation points to the real subtitle verification command and no longer implies a dedicated test lane exists when it does not.
|
||||
- [ ] #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 -->
|
||||
|
||||
1. Audit the existing subtitle-sync test surface, especially src/core/services/subsync.test.ts, and decide whether test:subtitle should reuse or regroup that coverage.
|
||||
2. Replace the placeholder script with a real automated command and keep the matrix legible alongside TASK-87.1 work.
|
||||
3. Update README or related docs so the advertised subtitle verification path matches reality.
|
||||
4. Verify both alass and ffsubsync behavior remain covered by the resulting lane.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
@@ -1,52 +0,0 @@
|
||||
---
|
||||
id: TASK-87.3
|
||||
title: >-
|
||||
Immersion tracking verification: make SQLite-backed persistence tests visible
|
||||
and reproducible
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:19'
|
||||
updated_date: '2026-03-06 03:21'
|
||||
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 -->
|
||||
|
||||
- [ ] #1 Database-backed immersion tracking tests run in at least one documented automated command that is practical for contributors or CI to execute.
|
||||
- [ ] #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.
|
||||
- [ ] #3 Contributor-facing documentation explains how to run the immersion tracker verification lane and any environment prerequisites it depends on.
|
||||
- [ ] #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 -->
|
||||
|
||||
1. Confirm which SQLite-backed immersion tests are currently skipped and why in the standard Bun environment.
|
||||
2. Establish a reproducible command or lane for the DB-backed cases, or make the unsupported-runtime limitation explicit and actionable.
|
||||
3. Document prerequisites and expected behavior for contributors and CI.
|
||||
4. Verify at least one persistence/finalization path beyond the seam tests is exercised by the new lane.
|
||||
<!-- SECTION:PLAN:END -->
|
||||
@@ -1,53 +0,0 @@
|
||||
---
|
||||
id: TASK-87.4
|
||||
title: >-
|
||||
Runtime composition root: remove dead symbols and tighten module boundaries in
|
||||
src/main.ts
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:19'
|
||||
updated_date: '2026-03-06 03:21'
|
||||
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 -->
|
||||
|
||||
- [ ] #1 src/main.ts no longer emits dead-symbol diagnostics under a noUnusedLocals/noUnusedParameters compile pass for the areas touched by this cleanup.
|
||||
- [ ] #2 Unused imports, destructured values, and stale locals identified in the current composition root are removed or relocated without behavior changes.
|
||||
- [ ] #3 The resulting composition root has clearer ownership boundaries for at least one runtime slice that is currently buried in the monolith.
|
||||
- [ ] #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 -->
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
id: TASK-87.5
|
||||
title: >-
|
||||
Dead architecture cleanup: delete unused registry and pipeline modules that
|
||||
are off the live path
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:20'
|
||||
updated_date: '2026-03-06 03:21'
|
||||
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 -->
|
||||
|
||||
- [ ] #1 Each candidate module identified in the review is either removed as dead code or justified and reconnected to a real supported execution path.
|
||||
- [ ] #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.
|
||||
- [ ] #3 The cleanup does not regress live tokenization or subtitle sync behavior and the relevant verification commands remain green.
|
||||
- [ ] #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 -->
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
id: TASK-87.6
|
||||
title: >-
|
||||
Anki integration maintainability: continue decomposing the oversized
|
||||
orchestration layer
|
||||
status: To Do
|
||||
assignee: []
|
||||
created_date: '2026-03-06 03:20'
|
||||
updated_date: '2026-03-06 03:21'
|
||||
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 -->
|
||||
|
||||
- [ ] #1 The responsibilities currently concentrated in src/anki-integration.ts are split into clearer modules or services with narrow ownership boundaries.
|
||||
- [ ] #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.
|
||||
- [ ] #3 Existing Anki integration behavior remains covered by automated verification, including note update, field grouping, and proxy-related flows that the refactor touches.
|
||||
- [ ] #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 -->
|
||||
@@ -1,51 +0,0 @@
|
||||
---
|
||||
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 -->
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -0,0 +1,44 @@
|
||||
# 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.
|
||||
```
|
||||
|
||||
For breaking changes, add `breaking: true`:
|
||||
|
||||
```md
|
||||
type: changed
|
||||
area: config
|
||||
breaking: true
|
||||
|
||||
- Renamed `foo.bar` to `foo.baz`.
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- `type` required: `added`, `changed`, `fixed`, `docs`, or `internal`
|
||||
- `area` required: short product area like `overlay`, `launcher`, `release`
|
||||
- `breaking` optional: set to `true` to flag as a breaking change
|
||||
- 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
|
||||
|
||||
How fragments turn into a release:
|
||||
|
||||
- At release time, `bun run changelog:build` (and `bun run changelog:prerelease-notes`) pipes every pending fragment through `claude -p` to merge related items, drop noise, and rewrite into a clean user-facing release body. Write fragments as raw, informative notes — don't worry about polished prose, deduping across PRs, or line-by-line phrasing. The polish step handles all of that.
|
||||
- `internal` fragments stay in `CHANGELOG.md` (inside a collapsed `<details>` block) but are dropped from the GitHub release notes entirely.
|
||||
- The polished `CHANGELOG.md` and `release/release-notes.md` are committed and reviewed before tagging — edit the Markdown by hand if Claude misses something.
|
||||
|
||||
Prerelease notes:
|
||||
|
||||
- prerelease tags like `v0.11.3-beta.1` and `v0.11.3-rc.1` reuse the current pending fragments to generate `release/prerelease-notes.md`
|
||||
- prerelease note generation does not consume fragments and does not update `CHANGELOG.md` or `docs-site/changelog.md`
|
||||
- the final stable release is the point where `bun run changelog:build` consumes fragments into the stable changelog and release notes
|
||||
@@ -0,0 +1,4 @@
|
||||
type: added
|
||||
area: updater
|
||||
|
||||
- Added tray and `subminer -u` update checks for SubMiner releases, including app update prompts, launcher updates, Linux rofi theme updates, checksum verification, configurable update notifications, and an opt-in prerelease update channel for beta/RC testing.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: character-dictionary
|
||||
|
||||
- Reused cached character-dictionary media matches so loading a title with an existing snapshot no longer sends another AniList search request.
|
||||
@@ -0,0 +1,6 @@
|
||||
type: added
|
||||
area: config
|
||||
|
||||
- Added a dedicated Configuration window with launcher entry points via `subminer --config` and `subminer config`.
|
||||
- Fixed the Configuration window preload so launcher-opened windows can initialize even when Electron sandboxing is active.
|
||||
- Kept config-window startup lightweight by skipping AniList token refresh and automatic update polling.
|
||||
@@ -0,0 +1,6 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- Controller config and debug shortcuts now stay closed while controller support is disabled and show a notice to enable `controller.enabled` manually.
|
||||
- Controller binding rows now start learn mode from the edit pencil, so clicking edit and pressing a controller button saves the remap.
|
||||
- Controller remaps are now saved per controller profile, binding badges also start learn mode, and row reset buttons restore individual bindings to their defaults.
|
||||
@@ -0,0 +1,5 @@
|
||||
type: added
|
||||
area: setup
|
||||
|
||||
- Added optional first-run setup controls to install Bun and the `subminer` command-line launcher on Linux, macOS, and Windows.
|
||||
- Added a Windows `subminer.cmd` user PATH shim so users can type `subminer` without adding `SubMiner.exe` to PATH.
|
||||
@@ -0,0 +1,6 @@
|
||||
type: fixed
|
||||
area: anilist
|
||||
|
||||
- Used fresh mpv time-position, duration, and subtitle timing events for AniList post-watch threshold checks so progress updates still fire when playback reaches or skips past the watched threshold.
|
||||
- Prefer season-specific AniList search results for multi-season files before falling back to the base title.
|
||||
- Show a clear AniList message when the matched season is not in Planning or Watching instead of silently queueing an impossible progress update.
|
||||
@@ -0,0 +1,10 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- Hid the macOS visible overlay when mpv is no longer the foreground target so other apps and Spaces are not covered by SubMiner subtitles.
|
||||
- Kept the macOS overlay layered above active mpv while stats mouse passthrough is enabled, and treated the frontmost mpv app as the focus signal.
|
||||
- Opened the stats overlay inactive on macOS so it appears over fullscreen mpv instead of switching back to SubMiner's original desktop.
|
||||
- Preserved the active mpv focus state through transient macOS helper misses so subtitles do not flicker while mpv remains foreground.
|
||||
- Kept fullscreen macOS overlays stable when mpv remains frontmost but window geometry temporarily disappears from the macOS window APIs.
|
||||
- Released the macOS overlay when the helper reports mpv is no longer foreground so other apps are no longer covered.
|
||||
- Reduced macOS window-tracker background work by preferring the compiled helper and slowing polls while mpv is stably focused.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- Fixed macOS overlay tracking so transient mpv window misses no longer hide the overlay; minimizing mpv still hides it.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- Fixed macOS overlay passthrough so mpv controls remain clickable before hovering subtitle bars.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- Fixed subtitle sync modal opens so macOS no longer flashes and hides the first modal attempt or leaves stale modal state after syncing.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: added
|
||||
area: launcher
|
||||
|
||||
- Added `subminer --version` and `subminer -v` to print the installed SubMiner app version.
|
||||
@@ -0,0 +1,5 @@
|
||||
type: fixed
|
||||
area: updater
|
||||
|
||||
- Made Linux `subminer -u` perform release updates from the launcher, independent of any running tray app instance, while reporting `up to date` without downloading assets when the latest release is not newer.
|
||||
- Limited support asset updates to the Linux rofi theme.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: launcher
|
||||
|
||||
- Fixed Linux first-run launcher installs by building the packaged launcher with a valid Bun shebang.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: updater
|
||||
|
||||
- Stopped Linux tray update checks from invoking the native Electron updater, using GitHub release metadata/assets instead so checks do not crash the tray app.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: setup
|
||||
|
||||
- First-run setup now recognizes installed macOS launchers in Homebrew or user PATH dirs, while manual setup installs avoid Homebrew-owned directories.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: updater
|
||||
|
||||
- Fixed tray update checks for builds that cannot install native app updates, showing a manual install message instead of a restart prompt that cannot apply the update.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: updater
|
||||
|
||||
- Bring macOS update dialogs to the front when `subminer --update` is run from the launcher.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: build
|
||||
|
||||
- Fixed one-shot `make clean build install` flows so install picks up the AppImage built earlier in the same make invocation.
|
||||
@@ -0,0 +1,6 @@
|
||||
type: fixed
|
||||
area: updates
|
||||
|
||||
- Restored the standard macOS `electron-updater`/Squirrel update path and routed supplemental GitHub updater requests through Electron networking instead of Node fetch.
|
||||
- macOS update checks now skip local build-output apps outside Applications before touching Squirrel, and macOS tray checks no longer perform the supplemental GitHub asset lookup.
|
||||
- macOS `electron-updater` metadata and full ZIP downloads now use `/usr/bin/curl` under the hood to avoid the Electron network crash seen during tray update checks while preserving Squirrel installation.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: changed
|
||||
area: release
|
||||
|
||||
- Prerelease note generation now reuses existing reviewed prerelease notes and asks Claude to merge only new fragment material, while `make clean` preserves `release/prerelease-notes.md`.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: internal
|
||||
area: tests
|
||||
|
||||
- Removed stale Yomitan vendor source-inspection assertions for changes that were not shipped.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: launcher
|
||||
|
||||
- Fixed `subminer app --setup` so it opens the setup flow when SubMiner is already running in the background.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: setup
|
||||
|
||||
- Quit standalone setup app launches after first-run setup finishes, returning the terminal instead of leaving the app process open.
|
||||
@@ -0,0 +1,10 @@
|
||||
type: fixed
|
||||
area: tray
|
||||
|
||||
- Kept the tray app running when closing tray-launched Yomitan settings.
|
||||
- Kept tray-launched Yomitan settings loading from blocking other tray actions.
|
||||
- Replaced the default native Yomitan settings menu with a close-only menu so closing settings does not quit the tray app.
|
||||
- Added an in-page close button for Yomitan settings on Hyprland, where native window controls are not available.
|
||||
- Disabled Yomitan's embedded popup preview in the tray-launched settings window to avoid renderer hangs during normal sidebar navigation.
|
||||
- Serialized copied Yomitan extension refreshes so startup cannot race itself and leave extension loading in an error state.
|
||||
- Fixed tray-launched session help focus handling so the modal can close without mpv running.
|
||||
@@ -2,22 +2,24 @@
|
||||
* SubMiner Example Configuration File
|
||||
*
|
||||
* This file is auto-generated from src/config/definitions.ts.
|
||||
* Copy to $XDG_CONFIG_HOME/SubMiner/config.jsonc (or ~/.config/SubMiner/config.jsonc) and edit as needed.
|
||||
* Copy to %APPDATA%/SubMiner/config.jsonc on Windows, or $XDG_CONFIG_HOME/SubMiner/config.jsonc (or ~/.config/SubMiner/config.jsonc) on Linux/macOS.
|
||||
*/
|
||||
{
|
||||
|
||||
// ==========================================
|
||||
// Overlay Auto-Start
|
||||
// When overlay connects to mpv, automatically show overlay and hide mpv subtitles.
|
||||
// ==========================================
|
||||
"auto_start_overlay": false, // When overlay connects to mpv, automatically show overlay and hide mpv subtitles. Values: true | false
|
||||
"auto_start_overlay": false, // Auto-start the subtitle overlay window when SubMiner launches. Values: true | false
|
||||
|
||||
// ==========================================
|
||||
// Texthooker Server
|
||||
// Control whether browser opens automatically for texthooker.
|
||||
// Configure texthooker startup launch and browser opening behavior.
|
||||
// ==========================================
|
||||
"texthooker": {
|
||||
"openBrowser": true, // Open browser setting. Values: true | false
|
||||
}, // Control whether browser opens automatically for texthooker.
|
||||
"launchAtStartup": false, // Launch texthooker server automatically when SubMiner starts. Values: true | false
|
||||
"openBrowser": false // Open the texthooker page in the default browser when the server starts. Values: true | false
|
||||
}, // Configure texthooker startup launch and browser opening behavior.
|
||||
|
||||
// ==========================================
|
||||
// WebSocket Server
|
||||
@@ -25,18 +27,146 @@
|
||||
// Auto mode disables built-in server if mpv_websocket is detected.
|
||||
// ==========================================
|
||||
"websocket": {
|
||||
"enabled": "auto", // Built-in subtitle websocket server mode. Values: auto | true | false
|
||||
"port": 6677, // Built-in subtitle websocket server port.
|
||||
"enabled": false, // Built-in subtitle websocket server mode. Values: auto | true | false
|
||||
"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": false, // 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.
|
||||
// Set to debug for full runtime diagnostics.
|
||||
// ==========================================
|
||||
"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.
|
||||
"level": "info" // Minimum log level for runtime logging. Values: debug | info | warn | error
|
||||
}, // Controls logging verbosity.
|
||||
|
||||
// ==========================================
|
||||
// Controller Support
|
||||
// Gamepad support for the visible overlay while keyboard-only mode is active.
|
||||
// Use Alt+C to pick a preferred controller and remap actions inline with learn mode.
|
||||
// Trigger input mode can be auto, digital-only, or analog-thresholded depending on the controller.
|
||||
// Override controller.buttonIndices when your pad reports non-standard raw button numbers.
|
||||
// ==========================================
|
||||
"controller": {
|
||||
"enabled": false, // Enable overlay controller support through the Chrome Gamepad API. Values: true | false
|
||||
"preferredGamepadId": "", // Preferred controller id saved from the controller config modal.
|
||||
"preferredGamepadLabel": "", // Preferred controller display label saved for diagnostics.
|
||||
"smoothScroll": true, // Use smooth scrolling for controller-driven popup scroll input. Values: true | false
|
||||
"scrollPixelsPerSecond": 900, // Base popup scroll speed for controller stick input.
|
||||
"horizontalJumpPixels": 160, // Popup page-jump distance for controller jump input.
|
||||
"stickDeadzone": 0.2, // Deadzone applied to controller stick axes.
|
||||
"triggerInputMode": "auto", // How controller triggers are interpreted: auto, pressed-only, or thresholded analog. Values: auto | digital | analog
|
||||
"triggerDeadzone": 0.5, // Minimum analog trigger value required when trigger input uses auto or analog mode.
|
||||
"repeatDelayMs": 320, // Delay before repeating held controller actions.
|
||||
"repeatIntervalMs": 120, // Repeat interval for held controller actions.
|
||||
"buttonIndices": {
|
||||
"select": 6, // Raw button index used for the controller select/minus/back button.
|
||||
"buttonSouth": 0, // Raw button index used for controller south/A button input.
|
||||
"buttonEast": 1, // Raw button index used for controller east/B button input.
|
||||
"buttonWest": 2, // Raw button index used for controller west/X button input.
|
||||
"buttonNorth": 3, // Raw button index used for controller north/Y button input.
|
||||
"leftShoulder": 4, // Raw button index used for controller left shoulder input.
|
||||
"rightShoulder": 5, // Raw button index used for controller right shoulder input.
|
||||
"leftStickPress": 9, // Raw button index used for controller L3 input.
|
||||
"rightStickPress": 10, // Raw button index used for controller R3 input.
|
||||
"leftTrigger": 6, // Raw button index used for controller L2 input.
|
||||
"rightTrigger": 7 // Raw button index used for controller R2 input.
|
||||
}, // Semantic button-name reference mapping used for legacy configs and debug output. Updating it does not rewrite existing raw binding descriptors.
|
||||
"bindings": {
|
||||
"toggleLookup": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 0 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for toggling lookup. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"closeLookup": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 1 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for closing lookup. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"toggleKeyboardOnlyMode": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 3 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for toggling keyboard-only mode. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"mineCard": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 2 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for mining the active card. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"quitMpv": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 6 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for quitting mpv. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"previousAudio": {
|
||||
"kind": "none" // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
}, // Controller binding descriptor for previous Yomitan audio. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"nextAudio": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 5 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for next Yomitan audio. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"playCurrentAudio": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 4 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for playing the current Yomitan audio. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"toggleMpvPause": {
|
||||
"kind": "button", // Discrete binding input source kind. When kind is "axis", set both axisIndex and direction. Values: none | button | axis
|
||||
"buttonIndex": 9 // Raw button index captured for this discrete controller action.
|
||||
}, // Controller binding descriptor for toggling mpv play/pause. Use Alt+C learn mode or set a raw button/axis descriptor manually. If kind is "axis", direction is required.
|
||||
"leftStickHorizontal": {
|
||||
"kind": "axis", // Analog binding input source kind. Values: none | axis
|
||||
"axisIndex": 0, // Raw axis index captured for this analog controller action.
|
||||
"dpadFallback": "horizontal" // Optional D-pad fallback used when this analog controller action should also read D-pad input. Values: none | horizontal | vertical
|
||||
}, // Axis binding descriptor used for left/right token selection. Use Alt+C learn mode or set a raw axis descriptor manually.
|
||||
"leftStickVertical": {
|
||||
"kind": "axis", // Analog binding input source kind. Values: none | axis
|
||||
"axisIndex": 1, // Raw axis index captured for this analog controller action.
|
||||
"dpadFallback": "vertical" // Optional D-pad fallback used when this analog controller action should also read D-pad input. Values: none | horizontal | vertical
|
||||
}, // Axis binding descriptor used for primary popup scrolling. Use Alt+C learn mode or set a raw axis descriptor manually.
|
||||
"rightStickHorizontal": {
|
||||
"kind": "axis", // Analog binding input source kind. Values: none | axis
|
||||
"axisIndex": 3, // Raw axis index captured for this analog controller action.
|
||||
"dpadFallback": "none" // Optional D-pad fallback used when this analog controller action should also read D-pad input. Values: none | horizontal | vertical
|
||||
}, // Axis binding descriptor reserved for alternate right-stick mappings. Use Alt+C learn mode or set a raw axis descriptor manually.
|
||||
"rightStickVertical": {
|
||||
"kind": "axis", // Analog binding input source kind. Values: none | axis
|
||||
"axisIndex": 4, // Raw axis index captured for this analog controller action.
|
||||
"dpadFallback": "none" // Optional D-pad fallback used when this analog controller action should also read D-pad input. Values: none | horizontal | vertical
|
||||
} // Axis binding descriptor used for popup page jumps. Use Alt+C learn mode or set a raw axis descriptor manually.
|
||||
}, // Raw controller binding descriptors saved by Alt+C learn mode. For discrete axis bindings, kind "axis" requires axisIndex and direction.
|
||||
"profiles": {} // Per-controller binding and button-index overrides keyed by the controller id reported by the Gamepad API.
|
||||
}, // Gamepad support for the visible overlay while keyboard-only mode is active.
|
||||
|
||||
// ==========================================
|
||||
// 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.
|
||||
|
||||
// ==========================================
|
||||
// Updates
|
||||
// Automatic update check behavior.
|
||||
// Manual checks from the tray or launcher are always allowed.
|
||||
// ==========================================
|
||||
"updates": {
|
||||
"enabled": true, // Run automatic update checks in the background. Values: true | false
|
||||
"checkIntervalHours": 24, // Minimum hours between automatic update checks.
|
||||
"notificationType": "system", // How SubMiner announces available updates. Values: system | osd | both | none
|
||||
"channel": "stable" // Release channel used for update checks. Values: stable | prerelease
|
||||
}, // Automatic update check behavior.
|
||||
|
||||
// ==========================================
|
||||
// Keyboard Shortcuts
|
||||
@@ -44,39 +174,163 @@
|
||||
// Hot-reload: shortcut changes apply live and update the session help modal on reopen.
|
||||
// ==========================================
|
||||
"shortcuts": {
|
||||
"toggleVisibleOverlayGlobal": "Alt+Shift+O", // Toggle visible overlay global setting.
|
||||
"copySubtitle": "CommandOrControl+C", // Copy subtitle setting.
|
||||
"copySubtitleMultiple": "CommandOrControl+Shift+C", // Copy subtitle multiple setting.
|
||||
"updateLastCardFromClipboard": "CommandOrControl+V", // Update last card from clipboard setting.
|
||||
"triggerFieldGrouping": "CommandOrControl+G", // Trigger field grouping setting.
|
||||
"triggerSubsync": "Ctrl+Alt+S", // Trigger subsync setting.
|
||||
"mineSentence": "CommandOrControl+S", // Mine sentence setting.
|
||||
"mineSentenceMultiple": "CommandOrControl+Shift+S", // Mine sentence multiple setting.
|
||||
"toggleVisibleOverlayGlobal": "Alt+Shift+O", // Global accelerator that toggles overlay visibility from anywhere on the system. Use null to disable.
|
||||
"copySubtitle": "CommandOrControl+C", // Accelerator that copies the current subtitle line to the clipboard.
|
||||
"copySubtitleMultiple": "CommandOrControl+Shift+C", // Accelerator that copies consecutive subtitle lines while the multi-copy window stays open.
|
||||
"updateLastCardFromClipboard": "CommandOrControl+V", // Accelerator that updates the last mined Anki card using the current clipboard contents.
|
||||
"triggerFieldGrouping": "CommandOrControl+G", // Accelerator that triggers Kiku field grouping on duplicate cards.
|
||||
"triggerSubsync": "Ctrl+Alt+S", // Accelerator that triggers subsync against the active subtitle file.
|
||||
"mineSentence": "CommandOrControl+S", // Accelerator that mines the current sentence as a new Anki card.
|
||||
"mineSentenceMultiple": "CommandOrControl+Shift+S", // Accelerator that mines consecutive sentences while the multi-mine window stays open.
|
||||
"multiCopyTimeoutMs": 3000, // Timeout for multi-copy/mine modes.
|
||||
"toggleSecondarySub": "CommandOrControl+Shift+V", // Toggle secondary sub setting.
|
||||
"markAudioCard": "CommandOrControl+Shift+A", // Mark audio card setting.
|
||||
"openRuntimeOptions": "CommandOrControl+Shift+O", // Open runtime options setting.
|
||||
"openJimaku": "Ctrl+Shift+J", // Open jimaku setting.
|
||||
"toggleSecondarySub": "CommandOrControl+Shift+V", // Accelerator that toggles the secondary subtitle bar visibility.
|
||||
"markAudioCard": "CommandOrControl+Shift+A", // Accelerator that marks the last mined card as an audio card.
|
||||
"openCharacterDictionary": "CommandOrControl+Alt+A", // Accelerator that opens the character dictionary modal.
|
||||
"openRuntimeOptions": "CommandOrControl+Shift+O", // Accelerator that opens the runtime options modal.
|
||||
"openJimaku": "Ctrl+Shift+J", // Accelerator that opens the Jimaku subtitle search modal.
|
||||
"openSessionHelp": "CommandOrControl+Slash", // Accelerator that opens the session help / keybinding cheatsheet.
|
||||
"openControllerSelect": "Alt+C", // Accelerator that opens the controller selection and learn-mode modal.
|
||||
"openControllerDebug": "Alt+Shift+C", // Accelerator that opens the controller debug modal with live axis/button readouts.
|
||||
"toggleSubtitleSidebar": "Backslash" // Accelerator that toggles the subtitle sidebar visibility.
|
||||
}, // Overlay keyboard shortcuts. Set a shortcut to null to disable.
|
||||
|
||||
// ==========================================
|
||||
// Keybindings (MPV Commands)
|
||||
// Extra keybindings that are merged with built-in defaults.
|
||||
// Default and custom keybindings that are merged with built-in defaults.
|
||||
// Set command to null to disable a default keybinding.
|
||||
// Hot-reload: keybinding changes apply live and update the session help modal on reopen.
|
||||
// ==========================================
|
||||
"keybindings": [], // Extra keybindings that are merged with built-in defaults.
|
||||
"keybindings": [
|
||||
{
|
||||
"key": "Space", // Key setting.
|
||||
"command": [
|
||||
"cycle",
|
||||
"pause"
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "KeyF", // Key setting.
|
||||
"command": [
|
||||
"cycle",
|
||||
"fullscreen"
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "KeyJ", // Key setting.
|
||||
"command": [
|
||||
"cycle",
|
||||
"sid"
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "Shift+KeyJ", // Key setting.
|
||||
"command": [
|
||||
"cycle",
|
||||
"secondary-sid"
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "ArrowRight", // Key setting.
|
||||
"command": [
|
||||
"seek",
|
||||
5
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "ArrowLeft", // Key setting.
|
||||
"command": [
|
||||
"seek",
|
||||
-5
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "ArrowUp", // Key setting.
|
||||
"command": [
|
||||
"seek",
|
||||
60
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "ArrowDown", // Key setting.
|
||||
"command": [
|
||||
"seek",
|
||||
-60
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "Shift+KeyH", // Key setting.
|
||||
"command": [
|
||||
"sub-seek",
|
||||
-1
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "Shift+KeyL", // Key setting.
|
||||
"command": [
|
||||
"sub-seek",
|
||||
1
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "Shift+BracketRight", // Key setting.
|
||||
"command": [
|
||||
"__sub-delay-next-line"
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "Shift+BracketLeft", // Key setting.
|
||||
"command": [
|
||||
"__sub-delay-prev-line"
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "Ctrl+Alt+KeyC", // Key setting.
|
||||
"command": [
|
||||
"__youtube-picker-open"
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "Ctrl+Alt+KeyP", // Key setting.
|
||||
"command": [
|
||||
"__playlist-browser-open"
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "Ctrl+Shift+KeyH", // Key setting.
|
||||
"command": [
|
||||
"__replay-subtitle"
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "Ctrl+Shift+KeyL", // Key setting.
|
||||
"command": [
|
||||
"__play-next-subtitle"
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "KeyQ", // Key setting.
|
||||
"command": [
|
||||
"quit"
|
||||
] // Command setting.
|
||||
},
|
||||
{
|
||||
"key": "Ctrl+KeyW", // Key setting.
|
||||
"command": [
|
||||
"quit"
|
||||
] // Command setting.
|
||||
}
|
||||
], // Default and custom keybindings that are merged with built-in defaults.
|
||||
|
||||
// ==========================================
|
||||
// Secondary Subtitles
|
||||
// Dual subtitle track options.
|
||||
// Used by subminer YouTube subtitle generation as secondary language preferences.
|
||||
// Used by managed subtitle loading as secondary language preferences for local and YouTube playback.
|
||||
// Hot-reload: defaultMode updates live while SubMiner is running.
|
||||
// ==========================================
|
||||
"secondarySub": {
|
||||
"secondarySubLanguages": [], // Secondary sub languages setting.
|
||||
"autoLoadSecondarySub": false, // Auto load secondary sub setting. Values: true | false
|
||||
"defaultMode": "hover", // Default mode setting.
|
||||
"secondarySubLanguages": [], // Language code priority list used to auto-select a secondary subtitle track when available.
|
||||
"autoLoadSecondarySub": false, // Automatically load a matching secondary subtitle when the primary subtitle loads. Values: true | false
|
||||
"defaultMode": "hover" // Default visibility mode for the secondary subtitle bar. Values: hidden | visible | hover
|
||||
}, // Dual subtitle track options.
|
||||
|
||||
// ==========================================
|
||||
@@ -85,10 +339,10 @@
|
||||
// ==========================================
|
||||
"subsync": {
|
||||
"defaultMode": "auto", // Subsync default mode. Values: auto | manual
|
||||
"alass_path": "", // Alass path setting.
|
||||
"ffsubsync_path": "", // Ffsubsync path setting.
|
||||
"ffmpeg_path": "", // Ffmpeg path setting.
|
||||
"replace": true, // Replace active subtitle file when synchronization succeeds.
|
||||
"alass_path": "", // Optional absolute path to the alass binary used by subsync. Leave empty to auto-discover from PATH.
|
||||
"ffsubsync_path": "", // Optional absolute path to the ffsubsync binary used by subsync. Leave empty to auto-discover from PATH.
|
||||
"ffmpeg_path": "", // Optional absolute path to the ffmpeg binary used by subsync. Leave empty to auto-discover from PATH.
|
||||
"replace": true // Replace the active subtitle file when sync completes. Values: true | false
|
||||
}, // Subsync engine and executable paths.
|
||||
|
||||
// ==========================================
|
||||
@@ -96,7 +350,7 @@
|
||||
// Initial vertical subtitle position from the bottom.
|
||||
// ==========================================
|
||||
"subtitlePosition": {
|
||||
"yPercent": 10, // Y percent setting.
|
||||
"yPercent": 10 // Vertical position of the subtitle overlay expressed as a percentage from the bottom of the screen.
|
||||
}, // Initial vertical subtitle position from the bottom.
|
||||
|
||||
// ==========================================
|
||||
@@ -105,12 +359,16 @@
|
||||
// Hot-reload: subtitle style changes apply live without restarting SubMiner.
|
||||
// ==========================================
|
||||
"subtitleStyle": {
|
||||
"primaryDefaultMode": "visible", // Default primary subtitle bar visibility mode. hidden hides it, visible shows it, hover reveals it on hover. Values: hidden | visible | hover
|
||||
"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": true, // 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.
|
||||
"fontFamily": "M PLUS 1 Medium, Source Han Sans JP, Noto Sans CJK JP", // Font family setting.
|
||||
"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": "Hiragino Sans, M PLUS 1, Source Han Sans JP, Noto Sans CJK JP", // Font family setting.
|
||||
"fontSize": 35, // Font size setting.
|
||||
"fontColor": "#cad3f5", // Font color setting.
|
||||
"fontWeight": "600", // Font weight setting.
|
||||
@@ -119,9 +377,9 @@
|
||||
"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.
|
||||
"textShadow": "0 2px 6px rgba(0,0,0,0.9), 0 0 12px rgba(0,0,0,0.55)", // Text shadow setting.
|
||||
"fontStyle": "normal", // Font style setting.
|
||||
"backgroundColor": "rgb(30, 32, 48, 0.88)", // Background color setting.
|
||||
"backgroundColor": "transparent", // Background color setting.
|
||||
"backdropFilter": "blur(6px)", // Backdrop filter setting.
|
||||
"nPlusOneColor": "#c6a0f6", // N plus one color setting.
|
||||
"knownWordColor": "#a6da95", // Known word color setting.
|
||||
@@ -129,17 +387,23 @@
|
||||
"N1": "#ed8796", // N1 setting.
|
||||
"N2": "#f5a97f", // N2 setting.
|
||||
"N3": "#f9e2af", // N3 setting.
|
||||
"N4": "#a6e3a1", // N4 setting.
|
||||
"N5": "#8aadf4", // N5 setting.
|
||||
"N4": "#8bd5ca", // N4 setting.
|
||||
"N5": "#8aadf4" // N5 setting.
|
||||
}, // 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.
|
||||
@@ -150,89 +414,131 @@
|
||||
"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.
|
||||
"textShadow": "0 2px 6px rgba(0,0,0,0.9), 0 0 12px rgba(0,0,0,0.55)", // Text shadow setting.
|
||||
"backgroundColor": "transparent", // Background color setting.
|
||||
"backdropFilter": "blur(6px)", // Backdrop filter setting.
|
||||
"fontWeight": "normal", // Font weight setting.
|
||||
"fontStyle": "normal", // Font style setting.
|
||||
}, // Secondary setting.
|
||||
"fontWeight": "600", // Font weight setting.
|
||||
"fontStyle": "normal" // Font style setting.
|
||||
} // Secondary setting.
|
||||
}, // Primary and secondary subtitle styling.
|
||||
|
||||
// ==========================================
|
||||
// Subtitle Sidebar
|
||||
// Parsed-subtitle sidebar cue list styling, behavior, and toggle key.
|
||||
// Hot-reload: subtitle sidebar changes apply live without restarting SubMiner.
|
||||
// ==========================================
|
||||
"subtitleSidebar": {
|
||||
"enabled": true, // Enable the subtitle sidebar feature for parsed subtitle sources. Values: true | false
|
||||
"autoOpen": false, // Automatically open the subtitle sidebar once during overlay startup. Values: true | false
|
||||
"layout": "overlay", // Render the subtitle sidebar as a floating overlay or reserve space inside mpv. Values: overlay | embedded
|
||||
"toggleKey": "Backslash", // KeyboardEvent.code used to toggle the subtitle sidebar open and closed.
|
||||
"pauseVideoOnHover": false, // Pause mpv while hovering the subtitle sidebar, then resume on leave. Values: true | false
|
||||
"autoScroll": true, // Auto-scroll the active subtitle cue into view while playback advances. Values: true | false
|
||||
"maxWidth": 420, // Maximum sidebar width in CSS pixels.
|
||||
"opacity": 0.95, // Base opacity applied to the sidebar shell.
|
||||
"backgroundColor": "rgba(73, 77, 100, 0.9)", // Background color for the subtitle sidebar shell.
|
||||
"textColor": "#cad3f5", // Default cue text color in the subtitle sidebar.
|
||||
"fontFamily": "\"M PLUS 1\", \"Noto Sans CJK JP\", sans-serif", // Font family used for subtitle sidebar cue text.
|
||||
"fontSize": 16, // Base font size for subtitle sidebar cue text in CSS pixels.
|
||||
"timestampColor": "#a5adcb", // Timestamp color in the subtitle sidebar.
|
||||
"activeLineColor": "#f5bde6", // Text color for the active subtitle cue.
|
||||
"activeLineBackgroundColor": "rgba(138, 173, 244, 0.22)", // Background color for the active subtitle cue.
|
||||
"hoverLineBackgroundColor": "rgba(54, 58, 79, 0.84)" // Background color for hovered subtitle cues.
|
||||
}, // Parsed-subtitle sidebar cue list styling, behavior, and toggle key.
|
||||
|
||||
// ==========================================
|
||||
// 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.
|
||||
"model": "openai/gpt-4o-mini", // Default model identifier requested from the shared AI provider.
|
||||
"baseUrl": "https://openrouter.ai/api", // Base URL for the shared OpenAI-compatible AI provider.
|
||||
"systemPrompt": "You are a translation engine. Return only the translated text with no explanations.", // Default system prompt sent with shared AI provider requests.
|
||||
"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": {
|
||||
"enabled": false, // Enable AnkiConnect integration. Values: true | false
|
||||
"url": "http://127.0.0.1:8765", // Url setting.
|
||||
"enabled": true, // Enable AnkiConnect integration. Values: true | false
|
||||
"url": "http://127.0.0.1:8765", // Base URL of the AnkiConnect HTTP server.
|
||||
"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.
|
||||
"upstreamUrl": "http://127.0.0.1:8765" // Upstream AnkiConnect URL proxied by local AnkiConnect proxy.
|
||||
}, // Proxy setting.
|
||||
"tags": ["SubMiner"], // Tags to add to cards mined or updated by SubMiner. Provide an empty array to disable automatic tagging.
|
||||
"tags": [
|
||||
"SubMiner"
|
||||
], // Tags to add to cards mined or updated by SubMiner. Provide an empty array to disable automatic tagging.
|
||||
"fields": {
|
||||
"audio": "ExpressionAudio", // Audio setting.
|
||||
"image": "Picture", // Image setting.
|
||||
"sentence": "Sentence", // Sentence setting.
|
||||
"miscInfo": "MiscInfo", // Misc info setting.
|
||||
"translation": "SelectionText", // Translation setting.
|
||||
"word": "Expression", // Card field for the mined word or expression text.
|
||||
"audio": "ExpressionAudio", // Card field that receives generated sentence audio.
|
||||
"image": "Picture", // Card field that receives the captured screenshot or animated image.
|
||||
"sentence": "Sentence", // Card field that receives the source sentence text.
|
||||
"miscInfo": "MiscInfo", // Card field that receives the miscellaneous info pattern (see ankiConnect.metadata.pattern).
|
||||
"translation": "SelectionText" // Card field that receives the current selection or translated text.
|
||||
}, // 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
|
||||
"generateImage": true, // Generate image setting. Values: true | false
|
||||
"imageType": "static", // Image type setting.
|
||||
"imageFormat": "jpg", // Image format setting.
|
||||
"imageQuality": 92, // Image quality setting.
|
||||
"animatedFps": 10, // Animated fps setting.
|
||||
"animatedMaxWidth": 640, // Animated max width setting.
|
||||
"animatedCrf": 35, // Animated crf setting.
|
||||
"audioPadding": 0.5, // Audio padding setting.
|
||||
"fallbackDuration": 3, // Fallback duration setting.
|
||||
"maxMediaDuration": 30, // Max media duration setting.
|
||||
"generateAudio": true, // Generate sentence audio for mined cards. Values: true | false
|
||||
"generateImage": true, // Generate screenshot or animated image for mined cards. Values: true | false
|
||||
"imageType": "static", // Image capture type: "static" for a single still frame, "avif" for an animated AVIF. Values: static | avif
|
||||
"imageFormat": "jpg", // Encoding format used when imageType is "static". Values: jpg | png | webp
|
||||
"imageQuality": 92, // Quality (0-100) used for lossy static image encoders.
|
||||
"animatedFps": 10, // Target frame rate for animated AVIF captures.
|
||||
"animatedMaxWidth": 640, // Maximum width applied to animated AVIF captures.
|
||||
"animatedCrf": 35, // Animated AVIF CRF quality target. Lower values produce larger, higher-quality files.
|
||||
"syncAnimatedImageToWordAudio": true, // For animated AVIF images, prepend a frozen first frame matching the existing word-audio duration so motion starts with sentence audio. Values: true | false
|
||||
"audioPadding": 0.5, // Seconds of padding appended to both ends of generated sentence audio.
|
||||
"fallbackDuration": 3, // Fallback clip duration in seconds when subtitle timing data is unavailable.
|
||||
"maxMediaDuration": 30 // Maximum allowed media clip duration in seconds.
|
||||
}, // Media setting.
|
||||
"behavior": {
|
||||
"overwriteAudio": true, // Overwrite audio setting. Values: true | false
|
||||
"overwriteImage": true, // Overwrite image setting. Values: true | false
|
||||
"mediaInsertMode": "append", // Media insert mode setting.
|
||||
"highlightWord": true, // Highlight word setting. Values: true | false
|
||||
"notificationType": "osd", // Notification type setting.
|
||||
"autoUpdateNewCards": true, // Automatically update newly added cards. Values: true | false
|
||||
}, // Behavior setting.
|
||||
"nPlusOne": {
|
||||
"knownWords": {
|
||||
"highlightEnabled": false, // Enable fast local highlighting for words already known in Anki. Values: true | false
|
||||
"refreshMinutes": 1440, // Minutes between known-word cache refreshes.
|
||||
"matchMode": "headword", // Known-word matching strategy for N+1 highlighting. Values: headword | surface
|
||||
"decks": [], // Decks used for N+1 known-word cache scope. Supports one or more deck names.
|
||||
"addMinedWordsImmediately": true, // Immediately append newly mined card words into the known-word cache. Values: true | false
|
||||
"matchMode": "headword", // Known-word matching strategy for subtitle annotations. Values: headword | surface
|
||||
"decks": {}, // Decks and fields for known-word cache. Object mapping deck names to arrays of field names to extract, e.g. { "Kaishi 1.5k": ["Word", "Word Reading"] }.
|
||||
"color": "#a6da95" // Color used for known-word highlights.
|
||||
}, // Known words setting.
|
||||
"behavior": {
|
||||
"overwriteAudio": true, // When updating an existing card, overwrite the audio field instead of skipping it. Values: true | false
|
||||
"overwriteImage": true, // When updating an existing card, overwrite the image field instead of skipping it. Values: true | false
|
||||
"mediaInsertMode": "append", // Whether new media is appended after or prepended before existing field contents on update. Values: append | prepend
|
||||
"highlightWord": true, // Bold the mined word inside the sentence field on the saved Anki card. Values: true | false
|
||||
"notificationType": "osd", // Notification surface used to announce mining and update outcomes. Values: osd | system | both | none
|
||||
"autoUpdateNewCards": true // Automatically update newly added cards. Values: true | false
|
||||
}, // Behavior setting.
|
||||
"nPlusOne": {
|
||||
"minSentenceWords": 3, // Minimum sentence word count required for N+1 targeting (default: 3).
|
||||
"nPlusOne": "#c6a0f6", // Color used for the single N+1 target token highlight.
|
||||
"knownWord": "#a6da95", // Color used for legacy known-word highlights.
|
||||
"nPlusOne": "#c6a0f6" // Color used for the single N+1 target token highlight.
|
||||
}, // N plus one setting.
|
||||
"metadata": {
|
||||
"pattern": "[SubMiner] %f (%t)", // Pattern setting.
|
||||
"pattern": "[SubMiner] %f (%t)" // Template used to render the miscInfo field. Placeholders include %f (filename) and %t (timestamp).
|
||||
}, // Metadata setting.
|
||||
"isLapis": {
|
||||
"enabled": false, // Enabled setting. Values: true | false
|
||||
"sentenceCardModel": "Japanese sentences", // Sentence card model setting.
|
||||
"enabled": false, // Enable Lapis-specific mining behaviors and sentence card model targeting. Values: true | false
|
||||
"sentenceCardModel": "Japanese sentences" // Note type name used by Lapis sentence cards.
|
||||
}, // Is lapis setting.
|
||||
"isKiku": {
|
||||
"enabled": false, // Enabled setting. Values: true | false
|
||||
"enabled": false, // Enable Kiku-specific mining behaviors (duplicate handling, field grouping). Values: true | false
|
||||
"fieldGrouping": "disabled", // Kiku duplicate-card field grouping mode. Values: auto | manual | disabled
|
||||
"deleteDuplicateInAuto": true, // Delete duplicate in auto setting. Values: true | false
|
||||
}, // Is kiku setting.
|
||||
"deleteDuplicateInAuto": true // When Kiku field grouping is "auto", delete the duplicate source card after grouping completes. Values: true | false
|
||||
} // Is kiku setting.
|
||||
}, // Automatic Anki updates and media generation options.
|
||||
|
||||
// ==========================================
|
||||
@@ -240,31 +546,67 @@
|
||||
// Jimaku API configuration and defaults.
|
||||
// ==========================================
|
||||
"jimaku": {
|
||||
"apiBaseUrl": "https://jimaku.cc", // Api base url setting.
|
||||
"apiBaseUrl": "https://jimaku.cc", // Base URL of the Jimaku subtitle search API.
|
||||
"languagePreference": "ja", // Preferred language used in Jimaku search. Values: ja | en | none
|
||||
"maxEntryResults": 10, // Maximum Jimaku search results returned.
|
||||
"maxEntryResults": 10 // Maximum Jimaku search results returned.
|
||||
}, // Jimaku API configuration and defaults.
|
||||
|
||||
// ==========================================
|
||||
// YouTube Subtitle Generation
|
||||
// Defaults for subminer YouTube subtitle extraction/transcription mode.
|
||||
// YouTube Playback Settings
|
||||
// Defaults for managed subtitle language preferences and YouTube subtitle loading.
|
||||
// ==========================================
|
||||
"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.
|
||||
"primarySubLanguages": ["ja", "jpn"], // Comma-separated primary subtitle language priority used by the launcher.
|
||||
}, // Defaults for subminer YouTube subtitle extraction/transcription mode.
|
||||
"youtube": {
|
||||
"primarySubLanguages": [
|
||||
"ja",
|
||||
"jpn"
|
||||
] // Comma-separated primary subtitle language priority for managed subtitle auto-selection.
|
||||
}, // Defaults for managed subtitle language preferences and YouTube subtitle loading.
|
||||
|
||||
// ==========================================
|
||||
// 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.
|
||||
|
||||
// ==========================================
|
||||
// Yomitan
|
||||
// Optional external Yomitan profile integration.
|
||||
// Setting yomitan.externalProfilePath switches SubMiner to read-only external-profile mode.
|
||||
// For GameSentenceMiner on Linux, the default overlay profile is usually ~/.config/gsm_overlay.
|
||||
// In external-profile mode SubMiner will not import, delete, or modify Yomitan dictionaries/settings.
|
||||
// ==========================================
|
||||
"yomitan": {
|
||||
"externalProfilePath": "" // Optional external Yomitan Electron profile path to use in read-only mode for shared dictionaries/settings. Example: ~/.config/gsm_overlay
|
||||
}, // Optional external Yomitan profile integration.
|
||||
|
||||
// ==========================================
|
||||
// MPV Launcher
|
||||
// Optional mpv.exe override for Windows playback entry points.
|
||||
// Set mpv.launchMode to choose normal, maximized, or fullscreen SubMiner-managed mpv playback.
|
||||
// Leave mpv.executablePath blank to auto-discover mpv.exe from SUBMINER_MPV_PATH or PATH.
|
||||
// ==========================================
|
||||
"mpv": {
|
||||
"executablePath": "", // Optional absolute path to mpv.exe for Windows launch flows. Leave empty to auto-discover from SUBMINER_MPV_PATH or PATH.
|
||||
"launchMode": "normal" // Default window state for SubMiner-managed mpv launches. Values: normal | maximized | fullscreen
|
||||
}, // Optional mpv.exe override for Windows playback entry points.
|
||||
|
||||
// ==========================================
|
||||
// Jellyfin
|
||||
// Optional Jellyfin integration for auth, browsing, and playback launch.
|
||||
@@ -274,10 +616,11 @@
|
||||
"jellyfin": {
|
||||
"enabled": false, // Enable optional Jellyfin integration and CLI control commands. Values: true | false
|
||||
"serverUrl": "", // Base Jellyfin server URL (for example: http://localhost:8096).
|
||||
"recentServers": [], // Recently authenticated Jellyfin server URLs shown in setup.
|
||||
"username": "", // Default Jellyfin username used during CLI login.
|
||||
"deviceId": "subminer", // Device id setting.
|
||||
"clientName": "SubMiner", // Client name setting.
|
||||
"clientVersion": "0.1.0", // Client version setting.
|
||||
"deviceId": "subminer", // Stable device identifier sent on the Jellyfin authentication handshake; primarily internal.
|
||||
"clientName": "SubMiner", // Client name sent on the Jellyfin authentication handshake; primarily internal.
|
||||
"clientVersion": "0.1.0", // Client version sent on the Jellyfin authentication handshake; primarily internal.
|
||||
"defaultLibraryId": "", // Optional default Jellyfin library ID for item listing.
|
||||
"remoteControlEnabled": true, // Enable Jellyfin remote cast control mode. Values: true | false
|
||||
"remoteControlAutoConnect": true, // Auto-connect to the configured remote control target. Values: true | false
|
||||
@@ -286,8 +629,16 @@
|
||||
"pullPictures": false, // Enable Jellyfin poster/icon fetching for launcher menus. Values: true | false
|
||||
"iconCacheDir": "/tmp/subminer-jellyfin-icons", // Directory used by launcher for cached Jellyfin poster icons.
|
||||
"directPlayPreferred": true, // Try direct play before server-managed transcoding when possible. Values: true | false
|
||||
"directPlayContainers": ["mkv", "mp4", "webm", "mov", "flac", "mp3", "aac"], // Container allowlist for direct play decisions.
|
||||
"transcodeVideoCodec": "h264", // Preferred transcode video codec when direct play is unavailable.
|
||||
"directPlayContainers": [
|
||||
"mkv",
|
||||
"mp4",
|
||||
"webm",
|
||||
"mov",
|
||||
"flac",
|
||||
"mp3",
|
||||
"aac"
|
||||
], // Container allowlist for direct play decisions.
|
||||
"transcodeVideoCodec": "h264" // Preferred transcode video codec when direct play is unavailable.
|
||||
}, // Optional Jellyfin integration for auth, browsing, and playback launch.
|
||||
|
||||
// ==========================================
|
||||
@@ -296,9 +647,10 @@
|
||||
// Uses official SubMiner Discord app assets for polished card visuals.
|
||||
// ==========================================
|
||||
"discordPresence": {
|
||||
"enabled": false, // Enable optional Discord Rich Presence updates. Values: true | false
|
||||
"enabled": true, // Enable optional Discord Rich Presence updates. Values: true | false
|
||||
"presenceStyle": "default", // Presence card text preset: "default" (clean bilingual), "meme" (Mining and crafting), "japanese" (fully JP), or "minimal".
|
||||
"updateIntervalMs": 3000, // Minimum interval between presence payload updates.
|
||||
"debounceMs": 750, // Debounce delay used to collapse bursty presence updates.
|
||||
"debounceMs": 750 // Debounce delay used to collapse bursty presence updates.
|
||||
}, // Optional Discord Rich Presence activity card updates for current playback/study session.
|
||||
|
||||
// ==========================================
|
||||
@@ -315,12 +667,33 @@
|
||||
"queueCap": 1000, // In-memory write queue cap before overflow policy applies.
|
||||
"payloadCapBytes": 256, // Max JSON payload size per event before truncation.
|
||||
"maintenanceIntervalMs": 86400000, // Maintenance cadence (prune + rollup + vacuum checks).
|
||||
"retentionMode": "preset", // Retention mode (`preset` uses preset values, `advanced` uses explicit values). Values: preset | advanced
|
||||
"retentionPreset": "balanced", // Retention preset when `retentionMode` is `preset`. Values: minimal | balanced | deep-history
|
||||
"retention": {
|
||||
"eventsDays": 7, // Raw event retention window in days.
|
||||
"telemetryDays": 30, // Telemetry retention window in days.
|
||||
"dailyRollupsDays": 365, // Daily rollup retention window in days.
|
||||
"monthlyRollupsDays": 1825, // Monthly rollup retention window in days.
|
||||
"vacuumIntervalDays": 7, // Minimum days between VACUUM runs.
|
||||
"eventsDays": 0, // Raw event retention window in days. Use 0 to keep all.
|
||||
"telemetryDays": 0, // Telemetry retention window in days. Use 0 to keep all.
|
||||
"sessionsDays": 0, // Session retention window in days. Use 0 to keep all.
|
||||
"dailyRollupsDays": 0, // Daily rollup retention window in days. Use 0 to keep all.
|
||||
"monthlyRollupsDays": 0, // Monthly rollup retention window in days. Use 0 to keep all.
|
||||
"vacuumIntervalDays": 0 // Minimum days between VACUUM runs. Use 0 to disable.
|
||||
}, // Retention setting.
|
||||
"lifetimeSummaries": {
|
||||
"global": true, // Maintain global lifetime stats rows. Values: true | false
|
||||
"anime": true, // Maintain per-anime lifetime stats rows. Values: true | false
|
||||
"media": true // Maintain per-media lifetime stats rows. Values: true | false
|
||||
} // Lifetime summaries setting.
|
||||
}, // Enable/disable immersion tracking.
|
||||
|
||||
// ==========================================
|
||||
// Stats Dashboard
|
||||
// Local immersion stats dashboard served on localhost and available as an in-app overlay.
|
||||
// Uses the immersion tracking database for overview, trends, sessions, and vocabulary views.
|
||||
// ==========================================
|
||||
"stats": {
|
||||
"toggleKey": "Backquote", // Key code to toggle the stats overlay.
|
||||
"markWatchedKey": "KeyW", // Key code to mark the current video as watched and advance to the next playlist entry.
|
||||
"serverPort": 6969, // Port for the stats HTTP server.
|
||||
"autoStartServer": true, // Automatically start the stats server on launch. Values: true | false
|
||||
"autoOpenBrowser": false // Automatically open the stats dashboard in a browser when the server starts. Values: true | false
|
||||
} // Local immersion stats dashboard served on localhost and available as an in-app overlay.
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
node_modules/
|
||||
.vitepress/cache/
|
||||
.vitepress/dist/
|
||||
.DS_Store
|
||||
.codex/
|
||||
.agents/
|
||||
**/CLAUDE.md
|
||||
.claude/*
|
||||
@@ -1,12 +1,36 @@
|
||||
const repositoryName = process.env.GITHUB_REPOSITORY?.split('/')[1];
|
||||
const base = process.env.GITHUB_ACTIONS && repositoryName ? `/${repositoryName}/` : '/';
|
||||
const DOCS_HOSTNAME = 'https://docs.subminer.moe';
|
||||
const PLAUSIBLE_PROXY_HOSTNAME = 'https://worker.sudacode.com';
|
||||
const PLAUSIBLE_SITE_SCRIPT_PATH = '/js/pa-h28Pn9ppgTJRmiSJlyPT6.js';
|
||||
const PLAUSIBLE_ENDPOINT = `${PLAUSIBLE_PROXY_HOSTNAME}/api/event`;
|
||||
const PLAUSIBLE_INIT_SCRIPT = [
|
||||
'window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};',
|
||||
`plausible.init({ endpoint: '${PLAUSIBLE_ENDPOINT}' });`,
|
||||
].join('\n');
|
||||
|
||||
function pageToCanonicalHref(page: string): string | null {
|
||||
if (page === '404.md') return null;
|
||||
|
||||
const route = page
|
||||
.replace(/(^|\/)index\.md$/, '')
|
||||
.replace(/\.md$/, '')
|
||||
.replace(/\/$/, '');
|
||||
return route ? `${DOCS_HOSTNAME}/${route}` : `${DOCS_HOSTNAME}/`;
|
||||
}
|
||||
|
||||
export default {
|
||||
title: 'SubMiner Docs',
|
||||
description:
|
||||
'SubMiner: an MPV immersion-mining overlay with Yomitan and AnkiConnect integration.',
|
||||
base,
|
||||
head: [
|
||||
['link', { rel: 'preconnect', href: PLAUSIBLE_PROXY_HOSTNAME }],
|
||||
[
|
||||
'script',
|
||||
{
|
||||
async: '',
|
||||
src: `${PLAUSIBLE_PROXY_HOSTNAME}${PLAUSIBLE_SITE_SCRIPT_PATH}`,
|
||||
},
|
||||
],
|
||||
['script', {}, PLAUSIBLE_INIT_SCRIPT],
|
||||
['link', { rel: 'icon', href: '/favicon.ico', sizes: 'any' }],
|
||||
[
|
||||
'link',
|
||||
@@ -38,7 +62,18 @@ export default {
|
||||
appearance: 'dark',
|
||||
cleanUrls: true,
|
||||
metaChunk: true,
|
||||
sitemap: { hostname: 'https://docs.subminer.moe' },
|
||||
sitemap: {
|
||||
hostname: DOCS_HOSTNAME,
|
||||
transformItems(items) {
|
||||
return items.filter(
|
||||
(item) => item.url !== 'README' && item.url !== `${DOCS_HOSTNAME}/README`,
|
||||
);
|
||||
},
|
||||
},
|
||||
transformHead({ page }) {
|
||||
const href = pageToCanonicalHref(page);
|
||||
return href ? [['link', { rel: 'canonical', href }]] : [];
|
||||
},
|
||||
lastUpdated: true,
|
||||
srcExclude: ['subagents/**'],
|
||||
markdown: {
|
||||
@@ -58,6 +93,7 @@ export default {
|
||||
{ text: 'Get Started', link: '/installation' },
|
||||
{ text: 'Mining', link: '/mining-workflow' },
|
||||
{ text: 'Configuration', link: '/configuration' },
|
||||
{ text: 'Changelog', link: '/changelog' },
|
||||
{ text: 'Troubleshooting', link: '/troubleshooting' },
|
||||
],
|
||||
sidebar: [
|
||||
@@ -66,10 +102,9 @@ export default {
|
||||
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: 'Launcher Script', link: '/launcher-script' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -77,20 +112,32 @@ export default {
|
||||
items: [
|
||||
{ text: 'Configuration', link: '/configuration' },
|
||||
{ text: 'Keyboard Shortcuts', link: '/shortcuts' },
|
||||
{ text: 'Anki Integration', link: '/anki-integration' },
|
||||
{ text: 'Jellyfin Integration', link: '/jellyfin-integration' },
|
||||
{ text: 'Subtitle Annotations', link: '/subtitle-annotations' },
|
||||
{ text: 'Subtitle Sidebar', link: '/subtitle-sidebar' },
|
||||
{ text: 'Immersion Tracking', link: '/immersion-tracking' },
|
||||
{ text: 'JLPT Vocabulary', link: '/jlpt-vocab-bundle' },
|
||||
{ text: 'MPV Plugin', link: '/mpv-plugin' },
|
||||
{ text: 'Troubleshooting', link: '/troubleshooting' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Integrations',
|
||||
items: [
|
||||
{ text: 'MPV Plugin', link: '/mpv-plugin' },
|
||||
{ text: 'Anki', link: '/anki-integration' },
|
||||
{ text: 'Jellyfin', link: '/jellyfin-integration' },
|
||||
{ text: 'YouTube', link: '/youtube-integration' },
|
||||
{ text: 'Jimaku', link: '/jimaku-integration' },
|
||||
{ text: 'AniList', link: '/anilist-integration' },
|
||||
{ text: 'Character Dictionary', link: '/character-dictionary' },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Development',
|
||||
items: [
|
||||
{ text: 'Building & Testing', link: '/development' },
|
||||
{ text: 'Architecture', link: '/architecture' },
|
||||
{ text: 'IPC + Runtime Contracts', link: '/ipc-contracts' },
|
||||
{ text: 'WebSocket + Texthooker API', link: '/websocket-texthooker-api' },
|
||||
{ text: 'Changelog', link: '/changelog' },
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -102,7 +149,7 @@ export default {
|
||||
copyright: 'Copyright © 2026-present sudacode',
|
||||
},
|
||||
editLink: {
|
||||
pattern: 'https://github.com/ksyasuda/SubMiner/edit/main/docs/:path',
|
||||
pattern: 'https://github.com/ksyasuda/SubMiner/edit/main/docs-site/:path',
|
||||
text: 'Edit this page on GitHub',
|
||||
},
|
||||
outline: { level: [2, 3], label: 'On this page' },
|
||||
@@ -0,0 +1,18 @@
|
||||
<script setup>
|
||||
import DefaultTheme from 'vitepress/theme';
|
||||
import StatusLine from './components/StatusLine.vue';
|
||||
import BlinkingCursor from './components/BlinkingCursor.vue';
|
||||
|
||||
const { Layout } = DefaultTheme;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Layout>
|
||||
<template #home-hero-info-after>
|
||||
<BlinkingCursor />
|
||||
</template>
|
||||
<template #layout-bottom>
|
||||
<StatusLine />
|
||||
</template>
|
||||
</Layout>
|
||||
</template>
|
||||
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
<span class="tui-cursor" aria-hidden="true">█</span>
|
||||
</template>
|
||||
@@ -0,0 +1,48 @@
|
||||
<script setup>
|
||||
import { useRoute, useData } from 'vitepress';
|
||||
import { computed } from 'vue';
|
||||
|
||||
const route = useRoute();
|
||||
const { page, frontmatter } = useData();
|
||||
|
||||
const mode = computed(() => {
|
||||
const layout = frontmatter.value.layout;
|
||||
if (layout === 'home') return 'HOME';
|
||||
return 'NORMAL';
|
||||
});
|
||||
|
||||
const filePath = computed(() => {
|
||||
const path = route.path;
|
||||
return path === '/' ? 'index.md' : `${path.replace(/^\//, '')}.md`;
|
||||
});
|
||||
|
||||
const section = computed(() => {
|
||||
const path = route.path;
|
||||
if (path === '/') return 'root';
|
||||
const parts = path.split('/').filter(Boolean);
|
||||
return parts[0] || 'root';
|
||||
});
|
||||
|
||||
const lastUpdated = computed(() => {
|
||||
if (!page.value.lastUpdated) return '';
|
||||
const date = new Date(page.value.lastUpdated);
|
||||
return date.toISOString().slice(0, 10);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<footer class="tui-statusline" aria-label="Page info">
|
||||
<div class="tui-statusline__left">
|
||||
<span class="tui-statusline__mode" :data-mode="mode">{{ mode }}</span>
|
||||
<span class="tui-statusline__sep"></span>
|
||||
<span class="tui-statusline__file">{{ filePath }}</span>
|
||||
</div>
|
||||
<div class="tui-statusline__right">
|
||||
<span class="tui-statusline__section">{{ section }}</span>
|
||||
<span class="tui-statusline__sep"></span>
|
||||
<span v-if="lastUpdated" class="tui-statusline__date">{{ lastUpdated }}</span>
|
||||
<span v-if="lastUpdated" class="tui-statusline__sep"></span>
|
||||
<span class="tui-statusline__branch">GPL-3.0</span>
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
@@ -2,7 +2,9 @@ import DefaultTheme from 'vitepress/theme';
|
||||
import { useRoute } from 'vitepress';
|
||||
import { nextTick, onMounted, watch } from 'vue';
|
||||
import '@catppuccin/vitepress/theme/macchiato/mauve.css';
|
||||
import './tui-theme.css';
|
||||
import './mermaid-modal.css';
|
||||
import TuiLayout from './TuiLayout.vue';
|
||||
|
||||
let mermaidLoader: Promise<any> | null = null;
|
||||
const MERMAID_MODAL_ID = 'mermaid-diagram-modal';
|
||||
@@ -112,6 +114,11 @@ async function getMermaid() {
|
||||
startOnLoad: false,
|
||||
securityLevel: 'loose',
|
||||
theme: 'base',
|
||||
flowchart: {
|
||||
padding: 16,
|
||||
nodeSpacing: 30,
|
||||
rankSpacing: 40,
|
||||
},
|
||||
themeVariables: {
|
||||
background: '#24273a',
|
||||
primaryColor: '#363a4f',
|
||||
@@ -177,7 +184,8 @@ async function renderMermaidBlocks() {
|
||||
}
|
||||
|
||||
export default {
|
||||
...DefaultTheme,
|
||||
Layout: TuiLayout,
|
||||
extends: DefaultTheme,
|
||||
setup() {
|
||||
const route = useRoute();
|
||||
const render = () => {
|
||||
@@ -1,11 +1,20 @@
|
||||
.mermaid-interactive {
|
||||
cursor: zoom-in;
|
||||
transition: outline-color 180ms ease;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mermaid-interactive svg {
|
||||
display: inline-block;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.mermaid-interactive:focus-visible {
|
||||
outline: 2px solid var(--vp-c-brand-1);
|
||||
outline-offset: 4px;
|
||||
border-radius: 6px;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.mermaid-modal {
|
||||
@@ -23,6 +32,8 @@
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.72);
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.mermaid-modal__dialog {
|
||||
@@ -32,9 +43,9 @@
|
||||
width: min(96vw, 1800px);
|
||||
max-height: 92vh;
|
||||
border: 1px solid var(--vp-c-border);
|
||||
border-radius: 12px;
|
||||
border-radius: 0;
|
||||
background: var(--vp-c-bg);
|
||||
box-shadow: var(--vp-shadow-4);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3), 0 24px 64px rgba(0, 0, 0, 0.2);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -44,11 +55,19 @@
|
||||
margin-right: 16px;
|
||||
margin-top: 12px;
|
||||
border: 1px solid var(--vp-c-border);
|
||||
border-radius: 6px;
|
||||
border-radius: 0;
|
||||
padding: 4px 10px;
|
||||
background: var(--vp-c-bg-soft);
|
||||
color: var(--vp-c-text-1);
|
||||
font-size: 14px;
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: border-color 180ms ease, color 180ms ease;
|
||||
}
|
||||
|
||||
.mermaid-modal__close:hover {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.mermaid-modal__content {
|
||||
@@ -0,0 +1,689 @@
|
||||
@import '@fontsource/jetbrains-mono/400.css';
|
||||
@import '@fontsource/jetbrains-mono/500.css';
|
||||
@import '@fontsource/jetbrains-mono/600.css';
|
||||
@import '@fontsource/jetbrains-mono/700.css';
|
||||
|
||||
@font-face {
|
||||
font-family: 'M PLUS 1';
|
||||
src: url('/assets/fonts/Mplus1-Medium.ttf') format('truetype');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Manrope Default';
|
||||
src: url('/assets/fonts/manrope-latin-600-normal.ttf') format('truetype');
|
||||
font-weight: 600;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
:root {
|
||||
--tui-font-mono: 'JetBrains Mono', 'Cascadia Code', 'Fira Code', monospace;
|
||||
--tui-font-body:
|
||||
'Manrope Default',
|
||||
'M PLUS 1',
|
||||
'Manrope',
|
||||
'Noto Sans CJK JP',
|
||||
'Noto Sans JP',
|
||||
'Hiragino Kaku Gothic ProN',
|
||||
'Meiryo',
|
||||
'Yu Gothic',
|
||||
'Hiragino Sans',
|
||||
system-ui,
|
||||
sans-serif;
|
||||
--tui-transition: 180ms ease;
|
||||
|
||||
/* Theme-specific values — overridden in .dark below */
|
||||
--tui-nav-bg: color-mix(in srgb, var(--vp-c-bg-alt) 88%, transparent);
|
||||
--tui-table-hover-bg: color-mix(in srgb, var(--vp-c-bg-soft) 80%, transparent);
|
||||
--tui-link-underline: color-mix(in srgb, var(--vp-c-brand-1) 40%, transparent);
|
||||
--tui-selection-bg: hsla(267, 83%, 45%, 0.14);
|
||||
--tui-hero-glow: hsla(267, 83%, 45%, 0.05);
|
||||
--tui-step-hover-bg: var(--vp-c-bg-alt);
|
||||
--tui-step-hover-glow: color-mix(in srgb, var(--vp-c-brand-1) 30%, transparent);
|
||||
}
|
||||
|
||||
.dark {
|
||||
--tui-nav-bg: hsla(232, 23%, 18%, 0.82);
|
||||
--tui-table-hover-bg: hsla(232, 23%, 18%, 0.4);
|
||||
--tui-link-underline: hsla(267, 83%, 80%, 0.3);
|
||||
--tui-selection-bg: hsla(267, 83%, 80%, 0.22);
|
||||
--tui-hero-glow: hsla(267, 83%, 80%, 0.06);
|
||||
--tui-step-hover-bg: hsla(232, 23%, 18%, 0.6);
|
||||
--tui-step-hover-glow: hsla(267, 83%, 80%, 0.3);
|
||||
}
|
||||
|
||||
:root {
|
||||
--vp-font-family-base: var(--tui-font-body);
|
||||
--vp-font-family-heading: var(--tui-font-body);
|
||||
--vp-font-family-mono: var(--tui-font-mono);
|
||||
}
|
||||
|
||||
.vp-doc {
|
||||
font-family: var(--vp-font-family-base);
|
||||
}
|
||||
|
||||
/* === Selection === */
|
||||
::selection {
|
||||
background: var(--tui-selection-bg);
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
/* === Scrollbar === */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--vp-c-divider);
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
/* === Global transitions === */
|
||||
a,
|
||||
button,
|
||||
.VPFeature,
|
||||
.VPNavBarMenuLink,
|
||||
.VPSidebarItem .text {
|
||||
transition: color var(--tui-transition), background var(--tui-transition),
|
||||
border-color var(--tui-transition), opacity var(--tui-transition);
|
||||
}
|
||||
|
||||
/* === Nav bar === */
|
||||
.VPNav .VPNavBarTitle .title {
|
||||
font-family: var(--tui-font-mono);
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.VPNav .VPNavBarMenuLink {
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.VPNav .VPNavBar {
|
||||
border-bottom: 1px solid var(--vp-c-divider);
|
||||
backdrop-filter: blur(12px) saturate(1.2);
|
||||
-webkit-backdrop-filter: blur(12px) saturate(1.2);
|
||||
}
|
||||
|
||||
.VPNav .VPNavBar:not(.has-sidebar) {
|
||||
background: var(--tui-nav-bg);
|
||||
}
|
||||
|
||||
.VPNav .VPNavBar.has-sidebar .content {
|
||||
backdrop-filter: blur(12px) saturate(1.2);
|
||||
-webkit-backdrop-filter: blur(12px) saturate(1.2);
|
||||
}
|
||||
|
||||
/* === Sidebar === */
|
||||
.VPSidebar .VPSidebarItem .text {
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem.is-active.is-link > .item .text::before {
|
||||
content: '> ';
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem.is-link:not(.is-active) > .item .text::before {
|
||||
content: ' ';
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem.is-link:not(.is-active) > .item:hover .text::before {
|
||||
content: '> ';
|
||||
color: var(--vp-c-text-3);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem.is-link:not(.is-active) > .item:hover .text {
|
||||
color: var(--vp-c-text-1) !important;
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem.level-0 > .item .text {
|
||||
font-weight: 700;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem.level-0 > .item .text::before {
|
||||
content: '# ';
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.VPSidebar .VPSidebarItem.level-0 .items {
|
||||
border-left: 1px solid var(--vp-c-divider);
|
||||
margin-left: 6px;
|
||||
padding-left: 12px;
|
||||
}
|
||||
|
||||
/* === Headings === */
|
||||
.vp-doc h1,
|
||||
.vp-doc h2,
|
||||
.vp-doc h3,
|
||||
.vp-doc h4 {
|
||||
font-family: var(--vp-font-family-heading);
|
||||
}
|
||||
|
||||
.vp-doc h1 {
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
.vp-doc h1::before {
|
||||
content: '> ';
|
||||
color: var(--vp-c-brand-1);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.vp-doc h2 {
|
||||
border-bottom: none;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.vp-doc h2::after {
|
||||
content: '';
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
height: 1px;
|
||||
background: repeating-linear-gradient(
|
||||
to right,
|
||||
var(--vp-c-divider) 0,
|
||||
var(--vp-c-divider) 1ch,
|
||||
transparent 1ch,
|
||||
transparent 1.5ch
|
||||
);
|
||||
}
|
||||
|
||||
/* === Inline code === */
|
||||
.vp-doc :not(pre) > code {
|
||||
border-radius: 0;
|
||||
padding: 2px 6px;
|
||||
font-size: 0.88em;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
color: var(--vp-c-brand-1);
|
||||
font-family: var(--tui-font-mono), 'M PLUS 1', 'Noto Sans CJK JP', 'Noto Sans JP',
|
||||
monospace;
|
||||
font-variant-ligatures: none;
|
||||
}
|
||||
|
||||
/* === Code blocks === */
|
||||
.vp-doc div[class*='language-'] {
|
||||
border-radius: 0;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
background: var(--vp-c-bg-alt) !important;
|
||||
font-family: var(--tui-font-mono), 'M PLUS 1', 'Noto Sans CJK JP', 'Noto Sans JP',
|
||||
monospace;
|
||||
font-variant-ligatures: none;
|
||||
}
|
||||
|
||||
.vp-doc div[class*='language-']::before {
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.vp-doc div[class*='language-'] > button.copy {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/* === Tables === */
|
||||
.vp-doc table {
|
||||
border-collapse: collapse;
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.vp-doc table th {
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.vp-doc table td,
|
||||
.vp-doc table th {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.vp-doc table tr:hover td {
|
||||
background: var(--tui-table-hover-bg);
|
||||
}
|
||||
|
||||
/* === Links === */
|
||||
.vp-doc a {
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid var(--tui-link-underline);
|
||||
transition: border-color var(--tui-transition), color var(--tui-transition);
|
||||
}
|
||||
|
||||
.vp-doc a:hover {
|
||||
border-bottom-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
/* === Custom containers === */
|
||||
.vp-doc .custom-block {
|
||||
border: 1px solid var(--vp-c-divider);
|
||||
border-radius: 0;
|
||||
padding: 16px 20px;
|
||||
margin: 16px 0;
|
||||
position: relative;
|
||||
background: var(--vp-c-bg-soft);
|
||||
}
|
||||
|
||||
.vp-doc .custom-block::before {
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.04em;
|
||||
text-transform: uppercase;
|
||||
position: absolute;
|
||||
top: -1px;
|
||||
left: 12px;
|
||||
transform: translateY(-50%);
|
||||
padding: 0 6px;
|
||||
background: var(--vp-c-bg);
|
||||
}
|
||||
|
||||
.vp-doc .custom-block.tip,
|
||||
.vp-doc .custom-block.info,
|
||||
.vp-doc .custom-block.warning,
|
||||
.vp-doc .custom-block.danger,
|
||||
.vp-doc .custom-block.details {
|
||||
border-left-width: 1px;
|
||||
}
|
||||
|
||||
.vp-doc .custom-block.tip { border-color: var(--vp-c-brand-1); }
|
||||
.vp-doc .custom-block.tip::before { content: '-- tip'; color: var(--vp-c-brand-1); }
|
||||
|
||||
.vp-doc .custom-block.info { border-color: var(--vp-c-brand-2); }
|
||||
.vp-doc .custom-block.info::before { content: '-- info'; color: var(--vp-c-brand-2); }
|
||||
|
||||
.vp-doc .custom-block.warning { border-color: var(--vp-c-warning-1); }
|
||||
.vp-doc .custom-block.warning::before { content: '-- warning'; color: var(--vp-c-warning-1); }
|
||||
|
||||
.vp-doc .custom-block.danger { border-color: var(--vp-c-danger-1); }
|
||||
.vp-doc .custom-block.danger::before { content: '-- danger'; color: var(--vp-c-danger-1); }
|
||||
|
||||
.vp-doc .custom-block.details { border-color: var(--vp-c-divider); }
|
||||
.vp-doc .custom-block.details::before { content: '-- details'; color: var(--vp-c-text-2); }
|
||||
|
||||
.vp-doc .custom-block .custom-block-title {
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* === Blockquotes === */
|
||||
.vp-doc blockquote {
|
||||
border-left: 2px solid var(--vp-c-divider);
|
||||
padding: 4px 0 4px 16px;
|
||||
margin: 16px 0;
|
||||
color: var(--vp-c-text-2);
|
||||
font-style: normal;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.vp-doc blockquote::before {
|
||||
content: '│';
|
||||
position: absolute;
|
||||
left: -2px;
|
||||
top: 4px;
|
||||
color: var(--vp-c-brand-1);
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 14px;
|
||||
line-height: 1;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* === Horizontal rules === */
|
||||
.vp-doc hr {
|
||||
border: none;
|
||||
height: 1px;
|
||||
margin: 24px 0;
|
||||
background: repeating-linear-gradient(
|
||||
to right,
|
||||
var(--vp-c-divider) 0,
|
||||
var(--vp-c-divider) 1ch,
|
||||
transparent 1ch,
|
||||
transparent 1.5ch
|
||||
);
|
||||
}
|
||||
|
||||
/* === Lists === */
|
||||
.vp-doc ul > li::marker {
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.vp-doc ol > li::marker {
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 0.85em;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
/* === Focus-visible === */
|
||||
:focus-visible {
|
||||
outline: 2px solid var(--vp-c-brand-1);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.vp-doc a:focus-visible {
|
||||
outline-offset: 3px;
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
|
||||
/* === Feature icon hover === */
|
||||
.VPFeatures .VPFeature .icon {
|
||||
transition: transform 280ms ease;
|
||||
}
|
||||
|
||||
.VPFeatures .VPFeature:hover .icon {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
/* === Blinking cursor === */
|
||||
.tui-cursor {
|
||||
display: inline-block;
|
||||
color: var(--vp-c-brand-1);
|
||||
animation: tui-blink 1s step-end infinite;
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 0.9em;
|
||||
vertical-align: baseline;
|
||||
margin-left: 2px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
@keyframes tui-blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
|
||||
/* === Statusline === */
|
||||
.tui-statusline {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 50;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 28px;
|
||||
padding: 0 12px;
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
background: var(--vp-c-bg-soft);
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
color: var(--vp-c-text-2);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tui-statusline__left,
|
||||
.tui-statusline__right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0;
|
||||
}
|
||||
|
||||
.tui-statusline__mode {
|
||||
padding: 0 10px;
|
||||
font-weight: 700;
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.04em;
|
||||
color: var(--vp-c-bg);
|
||||
background: var(--vp-c-brand-1);
|
||||
line-height: 28px;
|
||||
margin-left: -12px;
|
||||
}
|
||||
|
||||
.tui-statusline__mode[data-mode="HOME"] {
|
||||
background: var(--vp-c-brand-2);
|
||||
}
|
||||
|
||||
.tui-statusline__sep {
|
||||
display: inline-block;
|
||||
margin: 0 2px;
|
||||
color: var(--vp-c-divider);
|
||||
}
|
||||
|
||||
.tui-statusline__sep::after {
|
||||
content: '|';
|
||||
}
|
||||
|
||||
.tui-statusline__file {
|
||||
padding: 0 8px;
|
||||
color: var(--vp-c-text-1);
|
||||
}
|
||||
|
||||
.tui-statusline__section {
|
||||
padding: 0 8px;
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.tui-statusline__date {
|
||||
padding: 0 8px;
|
||||
color: var(--vp-c-text-3);
|
||||
}
|
||||
|
||||
.tui-statusline__branch {
|
||||
padding: 0 10px;
|
||||
font-weight: 600;
|
||||
color: var(--vp-c-bg);
|
||||
background: var(--vp-c-brand-3, var(--vp-c-brand-1));
|
||||
line-height: 28px;
|
||||
margin-right: -12px;
|
||||
}
|
||||
|
||||
body {
|
||||
padding-bottom: 28px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.tui-statusline__section,
|
||||
.tui-statusline__date {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.VPHome .VPHero::before {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* === Hero === */
|
||||
.VPHero .name {
|
||||
font-family: var(--tui-font-mono) !important;
|
||||
}
|
||||
|
||||
.VPHero .clip {
|
||||
-webkit-text-fill-color: var(--vp-c-brand-1) !important;
|
||||
}
|
||||
|
||||
.VPHero .text {
|
||||
font-family: var(--tui-font-mono) !important;
|
||||
font-size: 2rem !important;
|
||||
}
|
||||
|
||||
.VPHero .tagline {
|
||||
font-family: var(--tui-font-body);
|
||||
color: var(--vp-c-text-2);
|
||||
}
|
||||
|
||||
.VPHero .actions .action .VPButton {
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 13px;
|
||||
border-radius: 8px;
|
||||
text-transform: lowercase;
|
||||
transition: all var(--tui-transition);
|
||||
}
|
||||
|
||||
.VPHero .actions .action .VPButton:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.VPHero .actions .action .VPButton::before {
|
||||
content: '[';
|
||||
opacity: 0.5;
|
||||
margin-right: 2px;
|
||||
}
|
||||
|
||||
.VPHero .actions .action .VPButton::after {
|
||||
content: ']';
|
||||
opacity: 0.5;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.VPHero .image-container .image-bg {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* === Features === */
|
||||
.VPFeatures .VPFeature {
|
||||
border-radius: 8px !important;
|
||||
border: 1px solid var(--vp-c-divider) !important;
|
||||
transition: border-color var(--tui-transition), background var(--tui-transition),
|
||||
transform var(--tui-transition);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.VPFeatures .VPFeature::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
background: var(--vp-c-brand-1);
|
||||
transition: width 280ms ease;
|
||||
}
|
||||
|
||||
.VPFeatures .VPFeature:hover {
|
||||
border-color: var(--vp-c-brand-1) !important;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.VPFeatures .VPFeature:hover::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.VPFeatures .VPFeature .title {
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.VPFeatures .VPFeature .details {
|
||||
font-family: var(--tui-font-body);
|
||||
}
|
||||
|
||||
/* === Outline === */
|
||||
.VPDocAsideOutline .outline-title {
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.VPDocAsideOutline .outline-link {
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 12px;
|
||||
transition: color var(--tui-transition);
|
||||
}
|
||||
|
||||
.VPDocAsideOutline .outline-link.active {
|
||||
color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.VPDocAsideOutline .outline-link.active::before {
|
||||
content: '> ';
|
||||
}
|
||||
|
||||
/* === Doc footer === */
|
||||
.VPDocFooter .edit-link-button,
|
||||
.VPDocFooter .last-updated-text {
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link {
|
||||
border-radius: 0;
|
||||
transition: border-color var(--tui-transition);
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link:hover {
|
||||
border-color: var(--vp-c-brand-1);
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link .title {
|
||||
font-family: var(--tui-font-mono);
|
||||
}
|
||||
|
||||
.VPDocFooter .pager-link .desc {
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 11px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
/* === Search === */
|
||||
.VPLocalSearchBox .search-input {
|
||||
font-family: var(--tui-font-mono);
|
||||
}
|
||||
|
||||
/* === Background texture === */
|
||||
.VPDoc,
|
||||
.VPHome {
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.015'/%3E%3C/svg%3E");
|
||||
background-repeat: repeat;
|
||||
background-size: 200px 200px;
|
||||
}
|
||||
|
||||
/* === Hero glow === */
|
||||
.VPHome .VPHero {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.VPHome .VPHero::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -120px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 600px;
|
||||
height: 400px;
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
var(--tui-hero-glow) 0%,
|
||||
transparent 70%
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* === Footer === */
|
||||
.VPFooter {
|
||||
font-family: var(--tui-font-mono);
|
||||
font-size: 12px;
|
||||
border-top: 1px solid var(--vp-c-divider);
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
# SubMiner Docs
|
||||
|
||||
In-repo VitePress documentation source for SubMiner.
|
||||
|
||||
Internal architecture/workflow source of truth lives in `docs/README.md` at the repo root. Keep `docs-site/` user-facing.
|
||||
|
||||
## Local development
|
||||
|
||||
```bash
|
||||
bun --cwd docs-site install
|
||||
bun run docs:dev
|
||||
```
|
||||
|
||||
Build and preview:
|
||||
|
||||
```bash
|
||||
bun run docs:build
|
||||
bun run docs:preview
|
||||
bun run docs:test
|
||||
```
|
||||
|
||||
Direct package commands still work from `docs-site/` if you prefer:
|
||||
|
||||
```bash
|
||||
cd docs-site
|
||||
bun install
|
||||
bun run docs:dev
|
||||
```
|
||||
|
||||
## Cloudflare Pages
|
||||
|
||||
- Git repo: `ksyasuda/SubMiner`
|
||||
- Root directory: `docs-site`
|
||||
- Build command: `bun run docs:build`
|
||||
- Build output directory: `.vitepress/dist`
|
||||
- Build watch paths: `docs-site/*`
|
||||
|
||||
Cloudflare Pages watch paths use a single `*` wildcard for monorepo subdirectories. `docs-site/*` matches nested files under the docs site; `docs-site/**` can cause docs-only pushes to be skipped.
|
||||
@@ -0,0 +1,129 @@
|
||||
# AniList Integration
|
||||
|
||||
SubMiner can sync your watch progress to [AniList](https://anilist.co) automatically. When you finish an episode, SubMiner detects the title and episode number from the filename, finds the matching AniList entry, and updates your progress via the GraphQL API. Failed updates are retried with exponential backoff in the background.
|
||||
|
||||
AniList data also powers two additional features: [cover art](#cover-art) for the stats dashboard and the [Character Dictionary](/character-dictionary) for in-overlay name lookup.
|
||||
|
||||
## Setup
|
||||
|
||||
AniList integration is opt-in. To enable it:
|
||||
|
||||
1. Set `anilist.enabled` to `true` in your config.
|
||||
2. Leave `anilist.accessToken` empty and restart SubMiner (or run `--anilist-setup`).
|
||||
3. Approve access in the AniList authorization page.
|
||||
4. The callback returns to SubMiner via the `subminer://anilist-setup?...` protocol URL, and SubMiner stores the token automatically.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"anilist": {
|
||||
"enabled": true,
|
||||
"accessToken": "",
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
The access token is encrypted at rest using Electron's `safeStorage` API. On Linux this defaults to `gnome-libsecret`; override the backend with `--password-store=<backend>` (for example `--password-store=basic_text`).
|
||||
|
||||
If the embedded auth UI fails to render, SubMiner opens the authorize URL in your default browser and shows fallback instructions in-app.
|
||||
|
||||
::: tip
|
||||
You can also set `anilist.accessToken` directly in config to skip the setup flow entirely. When blank, SubMiner uses the locally stored encrypted token.
|
||||
:::
|
||||
|
||||
## How Tracking Works
|
||||
|
||||
SubMiner monitors playback and triggers an AniList progress update when an episode is considered "watched" -- at least 85% of the episode duration viewed and a minimum of 10 minutes watched.
|
||||
|
||||
The update flow:
|
||||
|
||||
1. **Title detection** -- SubMiner extracts the anime title, season, and episode number from the media filename. It tries [`guessit`](https://github.com/guessit-io/guessit) first for accurate parsing, then falls back to an internal filename parser if guessit is unavailable.
|
||||
2. **AniList search** -- The detected title is searched against the AniList GraphQL API. For season 2 and later files, SubMiner searches the season-specific title first, then falls back to the base title. SubMiner picks the best match by comparing titles (romaji, English, native) and filtering by episode count.
|
||||
3. **Progress check** -- SubMiner fetches your current list entry for the matched media. The media must already be in Planning or Watching; otherwise SubMiner shows an MPV message explaining that the update is not possible. If your recorded progress already meets or exceeds the detected episode, the update is skipped.
|
||||
4. **Mutation** -- A `SaveMediaListEntry` mutation sets the new progress and marks the entry as `CURRENT`.
|
||||
|
||||
## Update Queue and Retry
|
||||
|
||||
Failed AniList updates are persisted to a retry queue on disk and retried with exponential backoff.
|
||||
|
||||
| Parameter | Value |
|
||||
| ---------------- | ---------- |
|
||||
| Initial backoff | 30 seconds |
|
||||
| Maximum backoff | 6 hours |
|
||||
| Maximum attempts | 8 |
|
||||
| Queue capacity | 500 items |
|
||||
|
||||
After 8 failed attempts, the update is moved to a dead-letter queue and no longer retried automatically. The queue is persisted across restarts so no updates are lost if SubMiner exits before a retry succeeds.
|
||||
|
||||
Use `--anilist-retry-queue` to manually process one ready item from the queue.
|
||||
|
||||
## Cover Art
|
||||
|
||||
SubMiner fetches cover art from AniList for display in the stats dashboard. When a new video starts playing, the cover art fetcher:
|
||||
|
||||
1. Checks the local database for cached art.
|
||||
2. If missing, parses the media title (guessit then fallback) and searches the AniList API.
|
||||
3. Downloads the cover image from the AniList CDN and caches it locally (both URL and blob).
|
||||
4. Stores AniList metadata (romaji/English titles, total episodes) alongside the cover for dashboard display.
|
||||
|
||||
A no-match result is cached for 5 minutes before SubMiner retries, preventing repeated API calls for unrecognized media.
|
||||
|
||||
## Rate Limiting
|
||||
|
||||
All AniList API calls go through a shared rate limiter that enforces a sliding window of 20 requests per minute. The limiter also reads AniList's `X-RateLimit-Remaining` and `Retry-After` response headers and pauses requests when the server signals throttling. This applies to both episode tracking and cover art fetching.
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"anilist": {
|
||||
"enabled": true,
|
||||
"accessToken": "",
|
||||
"characterDictionary": {
|
||||
"enabled": false,
|
||||
"maxLoaded": 3,
|
||||
"profileScope": "all",
|
||||
"collapsibleSections": {
|
||||
"description": false,
|
||||
"characterInformation": false,
|
||||
"voicedBy": false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
| Option | Values | Description |
|
||||
| ------------------------------------------- | ------------------- | ------------------------------------------------------------------------------------------------------------ |
|
||||
| `enabled` | `true`, `false` | Enable AniList post-watch progress updates (default: `false`) |
|
||||
| `accessToken` | string | Explicit AniList access token override; when blank, SubMiner uses the stored encrypted token (default: `""`) |
|
||||
| `characterDictionary.enabled` | `true`, `false` | Enable auto-sync of the merged character dictionary from AniList (default: `false`) |
|
||||
| `characterDictionary.maxLoaded` | number | Number of recent media snapshots kept in the merged dictionary (default: `3`) |
|
||||
| `characterDictionary.profileScope` | `"all"`, `"active"` | Apply dictionary to all Yomitan profiles or only the active one |
|
||||
| `characterDictionary.collapsibleSections.*` | `true`, `false` | Control which dictionary entry sections start expanded |
|
||||
|
||||
See the [Character Dictionary](/character-dictionary) page for full details on the character dictionary feature, including name generation, matching, auto-sync lifecycle, and dictionary entry format.
|
||||
|
||||
## CLI Commands
|
||||
|
||||
| Command | Description |
|
||||
| ----------------------- | ------------------------------------------------------------- |
|
||||
| `--anilist-setup` | Open AniList setup/auth flow helper window |
|
||||
| `--anilist-status` | Print current token resolution state and retry queue counters |
|
||||
| `--anilist-logout` | Clear stored AniList token from local persisted state |
|
||||
| `--anilist-retry-queue` | Process one ready retry queue item immediately |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **Updates not triggering:** Confirm `anilist.enabled` is `true`. SubMiner requires at least 85% of the episode watched and a minimum of 10 minutes. Short episodes or partial watches will not trigger an update.
|
||||
- **Update not possible:** Add the season to your AniList Planning or Watching list first. SubMiner will not create new AniList list entries automatically.
|
||||
- **Wrong episode or title matched:** Detection quality is best when `guessit` is installed and on your `PATH`. Without it, SubMiner falls back to internal filename parsing which can be less accurate with unusual naming conventions.
|
||||
- **Token issues:** Run `--anilist-status` to check token state. If the token is invalid or expired, run `--anilist-setup` or `--anilist-logout` and re-authenticate.
|
||||
- **Updates failing repeatedly:** Run `--anilist-status` to see retry queue counters. Items that fail 8 times are moved to the dead-letter queue. Check network connectivity and AniList API status.
|
||||
- **Cover art missing:** Cover art is fetched on a best-effort basis using title matching. If the filename is hard to parse, the search may return no results. The fetcher retries after 5 minutes.
|
||||
- **Encryption unavailable on Linux:** If you see warnings about safeStorage, try `--password-store=basic_text` as a workaround, or ensure your desktop keyring (gnome-keyring, KWallet) is running.
|
||||
|
||||
## Related
|
||||
|
||||
- [Character Dictionary](/character-dictionary) -- AniList-powered character name dictionary for Yomitan
|
||||
- [Configuration Reference](/configuration) -- full config options
|
||||
- [Jellyfin Integration](/jellyfin-integration) -- media server integration
|
||||
@@ -13,10 +13,13 @@ AnkiConnect listens on `http://127.0.0.1:8765` by default. If you changed the po
|
||||
|
||||
## Auto-Enrichment Transport
|
||||
|
||||
SubMiner supports two auto-enrichment transport modes:
|
||||
When you add a word via Yomitan, SubMiner detects the new card and fills in the sentence, audio, image, and translation fields automatically. Two detection methods are available:
|
||||
|
||||
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).
|
||||
**Proxy mode** — SubMiner runs a local AnkiConnect-compatible proxy and intercepts card creation instantly. Recommended when possible.
|
||||
|
||||
**Polling mode** (default) — SubMiner polls AnkiConnect every few seconds for newly added cards. Simpler setup, but with a short delay (~3 seconds).
|
||||
|
||||
Use proxy mode if you want immediate enrichment. Use polling mode if your Yomitan instance is external (browser-based) or you prefer minimal configuration.
|
||||
|
||||
In both modes, the enrichment workflow is the same:
|
||||
|
||||
@@ -26,7 +29,8 @@ In both modes, the enrichment workflow is the same:
|
||||
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.
|
||||
Polling mode uses the query `"deck:<ankiConnect.deck>" added:1` to find recently added cards. If no deck is configured, it searches all decks.
|
||||
Known-word sync scope is controlled by `ankiConnect.knownWords.decks` (object map), with `ankiConnect.deck` used as legacy fallback.
|
||||
|
||||
### Proxy Mode Setup (Yomitan / Texthooker)
|
||||
|
||||
@@ -44,15 +48,12 @@ Polling mode uses the query `"deck:<your-deck>" added:1` to find recently added
|
||||
|
||||
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 **active bundled Yomitan profile** (`profiles[profileCurrent].options.anki.server`) to the active SubMiner endpoint:
|
||||
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
|
||||
|
||||
Server update behavior differs by mode:
|
||||
|
||||
- Proxy mode (`ankiConnect.proxy.enabled: true`): SubMiner force-syncs the bundled active profile to the proxy URL so `addNote` traffic goes through the local proxy and auto-enrichment can trigger.
|
||||
- Direct mode (`ankiConnect.proxy.enabled: false`): SubMiner only replaces blank/default server values (`http://127.0.0.1:8765`) to avoid overwriting custom direct-server setups.
|
||||
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`).
|
||||
|
||||
@@ -72,7 +73,7 @@ In Yomitan, go to Settings → Profile and:
|
||||
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. Bundled Yomitan profile sync behavior is described above (force-sync in proxy mode, conservative sync in direct mode).
|
||||
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)
|
||||
|
||||
@@ -114,6 +115,7 @@ SubMiner maps its data to your Anki note fields. Configure these under `ankiConn
|
||||
```jsonc
|
||||
"ankiConnect": {
|
||||
"fields": {
|
||||
"word": "Expression", // mined word / expression text
|
||||
"audio": "ExpressionAudio", // audio clip from the video
|
||||
"image": "Picture", // screenshot or animated clip
|
||||
"sentence": "Sentence", // subtitle text
|
||||
@@ -211,35 +213,49 @@ Animated AVIF requires an AV1 encoder (`libaom-av1`, `libsvtav1`, or `librav1e`)
|
||||
}
|
||||
```
|
||||
|
||||
`overwriteAudio` applies to automatic card updates and duplicate-card enrichment. Manual clipboard subtitle updates (`Ctrl/Cmd+C`, then `Ctrl/Cmd+V`) always replace generated sentence audio, while leaving the word audio field unchanged.
|
||||
|
||||
## 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.
|
||||
SubMiner can auto-translate the mined sentence and fill the translation field.
|
||||
Secondary subtitle text still wins when present. AI translation is only attempted when `ankiConnect.ai.enabled` is `true` and no secondary subtitle exists.
|
||||
|
||||
```jsonc
|
||||
"ai": {
|
||||
"enabled": true,
|
||||
"apiKey": "sk-...",
|
||||
"apiKeyCommand": "",
|
||||
"baseUrl": "https://openrouter.ai/api",
|
||||
"requestTimeoutMs": 15000
|
||||
},
|
||||
"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."
|
||||
"systemPrompt": "Translate mined sentence text only."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`ankiConnect.ai` controls feature-local enablement plus optional `model` / `systemPrompt` overrides.
|
||||
Provider credentials and request transport settings live in top-level `ai`.
|
||||
|
||||
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.
|
||||
1. If a secondary subtitle is available, use it as the translation.
|
||||
2. If `ankiConnect.ai.enabled` is `true` and top-level `ai.enabled` is `true`, call the shared AI provider.
|
||||
3. If AI translation fails and no secondary subtitle exists, fall back to the original sentence text.
|
||||
|
||||
The built-in translation request asks for English output by default. Customize that behavior through `ankiConnect.ai.systemPrompt`.
|
||||
|
||||
## 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.
|
||||
|
||||
::: warning Required config
|
||||
Sentence card creation and audio card marking both require `ankiConnect.isLapis.enabled: true` and a valid `sentenceCardModel` pointing to your Lapis/Kiku note type. Without this, the `Ctrl/Cmd+S` and `Ctrl/Cmd+Shift+A` shortcuts will not create cards.
|
||||
:::
|
||||
|
||||
```jsonc
|
||||
"ankiConnect": {
|
||||
"isLapis": {
|
||||
@@ -330,10 +346,8 @@ When you mine the same word multiple times, SubMiner can merge the cards instead
|
||||
},
|
||||
"ai": {
|
||||
"enabled": false,
|
||||
"apiKey": "",
|
||||
"model": "openai/gpt-4o-mini",
|
||||
"baseUrl": "https://openrouter.ai/api",
|
||||
"targetLanguage": "English",
|
||||
"systemPrompt": "Translate mined sentence text only.",
|
||||
},
|
||||
"isKiku": {
|
||||
"enabled": false,
|
||||
@@ -345,5 +359,12 @@ When you mine the same word multiple times, SubMiner can merge the cards instead
|
||||
"sentenceCardModel": "Japanese sentences",
|
||||
},
|
||||
},
|
||||
"ai": {
|
||||
"enabled": false,
|
||||
"apiKey": "",
|
||||
"apiKeyCommand": "",
|
||||
"baseUrl": "https://openrouter.ai/api",
|
||||
"requestTimeoutMs": 15000,
|
||||
},
|
||||
}
|
||||
```
|
||||
@@ -1,5 +1,7 @@
|
||||
# Architecture
|
||||
|
||||
This page is a contributor-facing architecture summary. Canonical internal architecture guidance lives in `docs/architecture/README.md` at the repo root.
|
||||
|
||||
SubMiner is split into three cooperating runtimes:
|
||||
|
||||
- Electron desktop app (`src/`) for overlay/UI/runtime orchestration.
|
||||
@@ -30,12 +32,14 @@ plugin/
|
||||
# state · messages · hover · ui · options · environment · log
|
||||
# binary · aniskip · aniskip_match)
|
||||
src/
|
||||
ai/ # AI translation provider utilities (client, config)
|
||||
main-entry.ts # Background-mode bootstrap wrapper before loading main.js
|
||||
main.ts # Entry point — delegates to runtime composers/domain modules
|
||||
preload.ts # Electron preload bridge
|
||||
types.ts # Shared type definitions
|
||||
main/ # Main-process composition/runtime adapters
|
||||
app-lifecycle.ts # App lifecycle + app-ready runtime runner factories
|
||||
character-dictionary-runtime.ts # Character-dictionary orchestration/public runtime API
|
||||
cli-runtime.ts # CLI command runtime service adapters
|
||||
config-validation.ts # Startup/hot-reload config error formatting and fail-fast helpers
|
||||
dependencies.ts # Shared dependency builders for IPC/runtime services
|
||||
@@ -50,6 +54,7 @@ src/
|
||||
startup-lifecycle.ts # Lifecycle runtime runner adapter
|
||||
state.ts # Application runtime state container + reducer transitions
|
||||
subsync-runtime.ts # Subsync command runtime adapter
|
||||
character-dictionary-runtime/ # Character-dictionary fetch/build/cache modules + focused tests
|
||||
runtime/
|
||||
composers/ # High-level composition clusters used by main.ts
|
||||
domains/ # Domain barrel exports (startup/overlay/mpv/jellyfin/...)
|
||||
@@ -69,7 +74,7 @@ src/
|
||||
handlers/ # Keyboard/mouse interaction modules
|
||||
modals/ # Jimaku/Kiku/subsync/runtime-options/session-help modals
|
||||
positioning/ # Subtitle position controller (drag-to-reposition)
|
||||
window-trackers/ # Backend-specific tracker implementations (Hyprland, Sway, X11, macOS)
|
||||
window-trackers/ # Backend-specific tracker implementations (Hyprland, Sway, X11, macOS, Windows)
|
||||
jimaku/ # Jimaku API integration helpers
|
||||
subsync/ # Subtitle sync (alass/ffsubsync) helpers
|
||||
subtitle/ # Subtitle processing utilities
|
||||
@@ -132,7 +137,7 @@ src/renderer/
|
||||
The main process orchestrates a single primary overlay window plus modal surfaces: `main.ts` delegates to composition modules that wire together domain services. Subtitle layers (primary + secondary bar) are rendered in the same overlay renderer process, connected through `preload.ts`. External runtimes (launcher CLI and mpv plugin) operate independently and communicate via IPC socket or CLI passthrough.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
flowchart TB
|
||||
classDef entry fill:#c6a0f6,stroke:#494d64,color:#24273a,stroke-width:2px,font-weight:bold
|
||||
classDef comp fill:#b7bdf8,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef svc fill:#8aadf4,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
@@ -142,43 +147,48 @@ flowchart LR
|
||||
classDef extrt fill:#eed49f,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
|
||||
subgraph ExtRt["External Runtimes"]
|
||||
Launcher["launcher/<br/>CLI dispatch"]:::extrt
|
||||
Plugin["subminer/init.lua<br/>mpv plugin"]:::extrt
|
||||
direction LR
|
||||
Launcher["Launcher CLI"]:::extrt
|
||||
Plugin["mpv Plugin"]:::extrt
|
||||
end
|
||||
|
||||
Main["main.ts"]:::entry
|
||||
|
||||
subgraph Comp["Composition"]
|
||||
direction LR
|
||||
Startup["Startup & Lifecycle"]:::comp
|
||||
Wiring["Runtime Wiring"]:::comp
|
||||
Composers["Domain Composers"]:::comp
|
||||
end
|
||||
|
||||
subgraph Svc["Services"]
|
||||
direction LR
|
||||
Mpv["MPV Stack"]:::svc
|
||||
OverlaySvc["Overlay Manager"]:::svc
|
||||
Mining["Mining & Subtitles"]:::svc
|
||||
AnkiProxy["Anki Proxy"]:::svc
|
||||
Integrations["Integrations"]:::svc
|
||||
Tracking["Tracking"]:::svc
|
||||
Config["Config & Options"]:::svc
|
||||
end
|
||||
|
||||
Bridge(["preload.ts"]):::bridge
|
||||
|
||||
subgraph Rend["Renderer"]
|
||||
direction LR
|
||||
OverlayWin["Overlay Window"]:::rend
|
||||
UI["Subtitles & Modals"]:::rend
|
||||
end
|
||||
|
||||
subgraph Ext["External Systems"]
|
||||
mpvExt["mpv player"]:::ext
|
||||
direction LR
|
||||
mpvExt["mpv"]:::ext
|
||||
AnkiExt["AnkiConnect"]:::ext
|
||||
JimakuExt["Jimaku API"]:::ext
|
||||
TrackerExt["Window Tracker<br/>Hyprland · Sway<br/>X11 · macOS"]:::ext
|
||||
AnilistExt["AniList API"]:::ext
|
||||
JimakuExt["Jimaku"]:::ext
|
||||
TrackerExt["Window Tracker"]:::ext
|
||||
AnilistExt["AniList"]:::ext
|
||||
JellyfinExt["Jellyfin"]:::ext
|
||||
DiscordExt["Discord RPC"]:::ext
|
||||
end
|
||||
|
||||
Main["main.ts<br/>composition root"]:::entry
|
||||
|
||||
subgraph Comp["Composition — src/main/"]
|
||||
Startup["Startup & Lifecycle<br/>startup · app-lifecycle<br/>startup-lifecycle · state"]:::comp
|
||||
Wiring["Runtime Wiring<br/>ipc-runtime · cli-runtime<br/>overlay-runtime"]:::comp
|
||||
Composers["Composers<br/>mpv · anilist<br/>jellyfin"]:::comp
|
||||
end
|
||||
|
||||
subgraph Svc["Services — src/core/services/"]
|
||||
Mpv["MPV Stack<br/>transport · protocol<br/>properties · metrics"]:::svc
|
||||
OverlaySvc["Overlay Manager<br/>window · visibility · bridge<br/>mpv-sub-visibility"]:::svc
|
||||
Mining["Mining & Subtitles<br/>mining · field-grouping<br/>subtitle-ws · tokenizer"]:::svc
|
||||
AnkiProxy["Anki Integration<br/>anki-connect-proxy<br/>note-update-workflow"]:::svc
|
||||
Integrations["Integrations<br/>jimaku · subsync<br/>texthooker · yomitan"]:::svc
|
||||
Tracking["Tracking<br/>anilist · jellyfin<br/>immersion · discord"]:::svc
|
||||
Config["Config & Runtime<br/>hot-reload<br/>runtime-options"]:::svc
|
||||
end
|
||||
|
||||
Bridge(["preload.ts<br/>Electron IPC"]):::bridge
|
||||
|
||||
subgraph Rend["Renderer — src/renderer/"]
|
||||
OverlayWin["Main overlay window<br/>primary + secondary subtitles"]:::rend
|
||||
UI["subtitle-render<br/>positioning<br/>handlers · modals"]:::rend
|
||||
DiscordExt["Discord"]:::ext
|
||||
end
|
||||
|
||||
Launcher -->|"CLI"| Main
|
||||
@@ -187,7 +197,10 @@ flowchart LR
|
||||
Main --> Comp
|
||||
Comp --> Svc
|
||||
|
||||
mpvExt <-->|"JSON socket"| Mpv
|
||||
Svc --> Bridge
|
||||
Bridge --> Rend
|
||||
|
||||
mpvExt <-->|"socket"| Mpv
|
||||
AnkiExt <-->|"HTTP"| AnkiProxy
|
||||
JimakuExt <-->|"HTTP"| Integrations
|
||||
TrackerExt <-->|"platform"| OverlaySvc
|
||||
@@ -195,10 +208,6 @@ flowchart LR
|
||||
JellyfinExt <-->|"HTTP"| Tracking
|
||||
DiscordExt <-->|"RPC"| Integrations
|
||||
|
||||
OverlaySvc & Mining --> Bridge
|
||||
Bridge --> OverlayWin
|
||||
OverlayWin --> UI
|
||||
|
||||
style Comp fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
style Svc fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
style Rend fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
@@ -271,7 +280,7 @@ For domains migrated to reducer-style transitions (for example AniList token/que
|
||||
- **Shutdown:** `onWillQuitCleanup` destroys tray + config watcher, unregisters shortcuts, stops WebSocket + texthooker servers, closes the mpv socket + flushes OSD log, stops the window tracker, closes the Yomitan parser window, flushes the immersion tracker (SQLite), stops Jellyfin/Discord services, stops the AnkiConnect proxy server, and cleans Anki/AniList state.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
flowchart TB
|
||||
classDef start fill:#c6a0f6,stroke:#494d64,color:#24273a,stroke-width:2px,font-weight:bold
|
||||
classDef phase fill:#b7bdf8,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef decision fill:#f5a97f,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
@@ -280,71 +289,88 @@ flowchart LR
|
||||
classDef shutdown fill:#ed8796,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef warmup fill:#eed49f,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
|
||||
CLI["CLI args &<br/>environment"]:::start
|
||||
CLI --> Proto["Module-level init<br/>register protocols<br/>construct services<br/>wire deps"]:::phase
|
||||
Proto --> Parse["startup.ts<br/>parse argv<br/>detect backend"]:::phase
|
||||
Parse --> GenCheck{"--generate<br/>-config?"}:::decision
|
||||
GenCheck -->|"yes"| GenExit["Write template<br/>& exit"]:::phase
|
||||
GenCheck -->|"no"| Lock["app-lifecycle.ts<br/>single-instance lock<br/>lifecycle hooks"]:::phase
|
||||
CLI["CLI + Environment"]:::start
|
||||
CLI --> Init["Module Init"]:::phase
|
||||
Init --> Parse["Parse argv"]:::phase
|
||||
Parse --> GenCheck{"--generate-config?"}:::decision
|
||||
GenCheck -->|"yes"| GenExit["Write & exit"]:::phase
|
||||
GenCheck -->|"no"| Lock["Acquire lock"]:::phase
|
||||
|
||||
Lock -->|"app.whenReady()"| Ready["composeAppReady<br/>Runtime()"]:::phase
|
||||
Lock -->|"app.whenReady()"| Ready["App Ready"]:::phase
|
||||
|
||||
Ready --> Config["Config reload<br/>keybindings<br/>log level"]:::init
|
||||
Ready --> MpvInit["MpvIpcClient<br/>connect socket<br/>subscribe 26 props"]:::init
|
||||
Ready --> Platform["RuntimeOptions<br/>timing tracker<br/>immersion tracker"]:::init
|
||||
Ready --> Config["Config + keybindings"]:::init
|
||||
Ready --> MpvInit["MPV socket connect"]:::init
|
||||
Ready --> Platform["Runtime services"]:::init
|
||||
|
||||
Config --> OverlayInit
|
||||
MpvInit --> OverlayInit
|
||||
Platform --> OverlayInit
|
||||
Config & MpvInit & Platform --> OverlayInit["Overlay Init"]:::phase
|
||||
|
||||
OverlayInit["initializeOverlay<br/>Runtime()"]:::phase
|
||||
OverlayInit --> MainWin["Create window"]:::init
|
||||
OverlayInit --> Shortcuts["Register shortcuts"]:::init
|
||||
|
||||
OverlayInit --> MainWin["Main overlay window<br/>primary + secondary subtitles"]:::init
|
||||
OverlayInit --> Shortcuts["Register global<br/>shortcuts"]:::init
|
||||
MainWin & Shortcuts --> Warmups
|
||||
|
||||
MainWin --> Warmups
|
||||
Shortcuts --> Warmups
|
||||
|
||||
Warmups["Background<br/>warmups"]:::phase
|
||||
|
||||
subgraph WarmupGroup[" "]
|
||||
direction TB
|
||||
W1["MeCab<br/>+ worker thread"]:::warmup
|
||||
W2["Yomitan"]:::warmup
|
||||
W3["JLPT + freq<br/>dictionaries"]:::warmup
|
||||
W4["Jellyfin"]:::warmup
|
||||
W5["Discord"]:::warmup
|
||||
W6["AniList"]:::warmup
|
||||
W7["AnkiConnect<br/>proxy"]:::warmup
|
||||
W1 ~~~ W2 ~~~ W3 ~~~ W4 ~~~ W5 ~~~ W6 ~~~ W7
|
||||
subgraph Warmups["Background Warmups (parallel)"]
|
||||
direction LR
|
||||
W1["MeCab"]:::warmup ~~~ W2["Yomitan"]:::warmup ~~~ W3["Dictionaries"]:::warmup ~~~ W4["Jellyfin"]:::warmup ~~~ W5["Discord"]:::warmup ~~~ W6["AniList"]:::warmup ~~~ W7["Anki Proxy"]:::warmup
|
||||
end
|
||||
|
||||
Warmups --> WarmupGroup
|
||||
Warmups --> Loop
|
||||
|
||||
subgraph Loop["Runtime — event-driven"]
|
||||
subgraph Loop["Event Loop"]
|
||||
direction TB
|
||||
MpvEvt["mpv events: subtitle · timing · metrics"]:::runtime
|
||||
IpcEvt["IPC: renderer requests · CLI commands"]:::runtime
|
||||
ExtEvt["Shortcuts · config hot-reload"]:::runtime
|
||||
MpvEvt & IpcEvt & ExtEvt --> Route["Route via composers"]:::runtime
|
||||
Route --> Process["SubtitlePipeline<br/>normalize → tokenize → merge"]:::runtime
|
||||
Process --> Broadcast["Update AppState<br/>broadcast to renderer + modals"]:::runtime
|
||||
Events["mpv · IPC · shortcuts · config"]:::runtime
|
||||
Events --> Route["Composers"]:::runtime
|
||||
Route --> Pipeline["Subtitle Pipeline"]:::runtime
|
||||
Pipeline --> Broadcast["State + Renderer"]:::runtime
|
||||
end
|
||||
|
||||
WarmupGroup --> Loop
|
||||
style Warmups fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
|
||||
style WarmupGroup fill:transparent,stroke:none
|
||||
Loop -->|"quit"| Quit["Shutdown"]:::shutdown
|
||||
|
||||
Loop -->|"quit signal"| Quit["will-quit"]:::shutdown
|
||||
subgraph Cleanup[" "]
|
||||
direction LR
|
||||
T1["UI cleanup"]:::shutdown
|
||||
T2["Socket + server teardown"]:::shutdown
|
||||
T3["Flush tracking + state"]:::shutdown
|
||||
end
|
||||
|
||||
Quit --> T1["Tray · config watcher<br/>global shortcuts"]:::shutdown
|
||||
Quit --> T2["WebSocket · texthooker<br/>mpv socket · OSD log"]:::shutdown
|
||||
Quit --> T3["Window tracker<br/>Yomitan parser"]:::shutdown
|
||||
Quit --> T4["Immersion tracker<br/>Jellyfin · Discord<br/>Anki proxy · AniList"]:::shutdown
|
||||
Quit --> Cleanup
|
||||
|
||||
style Cleanup fill:transparent,stroke:none
|
||||
style Loop fill:#363a4f,stroke:#494d64,color:#cad3f5
|
||||
```
|
||||
|
||||
## Subtitle Prefetch Pipeline
|
||||
|
||||
SubMiner can pre-tokenize upcoming subtitle lines before they appear on screen. When an external subtitle file (SRT, VTT, or ASS) is detected on the active track, the `SubtitlePrefetchService` parses all cues via the `SubtitleCueParser`, identifies a priority window of upcoming lines based on the current playback position, and tokenizes them in the background through the same pipeline used for live subtitles. Results are stored directly into the `SubtitleProcessingController` cache, so when a subtitle actually appears during playback, it hits a warm cache and renders in ~30-50ms instead of ~200-320ms.
|
||||
|
||||
The prefetcher yields to live subtitle processing (which always takes priority over background work) and re-computes its priority window on seek. Cache invalidation events (e.g. marking a word as known) trigger re-prefetching of the current window to keep results fresh.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
classDef phase fill:#b7bdf8,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef init fill:#8aadf4,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef runtime fill:#8bd5ca,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
classDef warmup fill:#eed49f,stroke:#494d64,color:#24273a,stroke-width:1.5px
|
||||
|
||||
SubFile["External Sub File"]:::init
|
||||
Parse["Cue Parser"]:::phase
|
||||
Window["Upcoming Lines"]:::phase
|
||||
Tokenize["Pre-tokenize"]:::warmup
|
||||
Cache["Token Cache"]:::runtime
|
||||
Appear["Subtitle Appears"]:::init
|
||||
Hit["Cache Hit"]:::runtime
|
||||
Render["Fast Render"]:::runtime
|
||||
|
||||
SubFile --> Parse --> Window --> Tokenize --> Cache
|
||||
Appear --> Hit --> Render
|
||||
Cache -.->|"warm"| Hit
|
||||
|
||||
style SubFile stroke-width:2px
|
||||
style Render stroke-width:2px
|
||||
```
|
||||
|
||||
## Why This Design
|
||||
|
||||
- **Smaller blast radius:** changing one feature usually touches one service.
|
||||
@@ -0,0 +1,623 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "subminer-docs",
|
||||
"dependencies": {
|
||||
"@catppuccin/vitepress": "^0.1.2",
|
||||
"@fontsource/jetbrains-mono": "^5.2.8",
|
||||
"@fontsource/manrope": "^5.2.8",
|
||||
"mermaid": "^11.12.3",
|
||||
},
|
||||
"devDependencies": {
|
||||
"vitepress": "^1.6.4",
|
||||
},
|
||||
},
|
||||
},
|
||||
"packages": {
|
||||
"@algolia/abtesting": ["@algolia/abtesting@1.15.1", "", { "dependencies": { "@algolia/client-common": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", "@algolia/requester-fetch": "5.49.1", "@algolia/requester-node-http": "5.49.1" } }, "sha512-2yuIC48rUuHGhU1U5qJ9kJHaxYpJ0jpDHJVI5ekOxSMYXlH4+HP+pA31G820lsAznfmu2nzDV7n5RO44zIY1zw=="],
|
||||
|
||||
"@algolia/autocomplete-core": ["@algolia/autocomplete-core@1.17.7", "", { "dependencies": { "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", "@algolia/autocomplete-shared": "1.17.7" } }, "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q=="],
|
||||
|
||||
"@algolia/autocomplete-plugin-algolia-insights": ["@algolia/autocomplete-plugin-algolia-insights@1.17.7", "", { "dependencies": { "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { "search-insights": ">= 1 < 3" } }, "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A=="],
|
||||
|
||||
"@algolia/autocomplete-preset-algolia": ["@algolia/autocomplete-preset-algolia@1.17.7", "", { "dependencies": { "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", "algoliasearch": ">= 4.9.1 < 6" } }, "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA=="],
|
||||
|
||||
"@algolia/autocomplete-shared": ["@algolia/autocomplete-shared@1.17.7", "", { "peerDependencies": { "@algolia/client-search": ">= 4.9.1 < 6", "algoliasearch": ">= 4.9.1 < 6" } }, "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg=="],
|
||||
|
||||
"@algolia/client-abtesting": ["@algolia/client-abtesting@5.49.1", "", { "dependencies": { "@algolia/client-common": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", "@algolia/requester-fetch": "5.49.1", "@algolia/requester-node-http": "5.49.1" } }, "sha512-h6M7HzPin+45/l09q0r2dYmocSSt2MMGOOk5c4O5K/bBBlEwf1BKfN6z+iX4b8WXcQQhf7rgQwC52kBZJt/ZZw=="],
|
||||
|
||||
"@algolia/client-analytics": ["@algolia/client-analytics@5.49.1", "", { "dependencies": { "@algolia/client-common": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", "@algolia/requester-fetch": "5.49.1", "@algolia/requester-node-http": "5.49.1" } }, "sha512-048T9/Z8OeLmTk8h76QUqaNFp7Rq2VgS2Zm6Y2tNMYGQ1uNuzePY/udB5l5krlXll7ZGflyCjFvRiOtlPZpE9g=="],
|
||||
|
||||
"@algolia/client-common": ["@algolia/client-common@5.49.1", "", {}, "sha512-vp5/a9ikqvf3mn9QvHN8PRekn8hW34aV9eX+O0J5mKPZXeA6Pd5OQEh2ZWf7gJY6yyfTlLp5LMFzQUAU+Fpqpg=="],
|
||||
|
||||
"@algolia/client-insights": ["@algolia/client-insights@5.49.1", "", { "dependencies": { "@algolia/client-common": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", "@algolia/requester-fetch": "5.49.1", "@algolia/requester-node-http": "5.49.1" } }, "sha512-B6N7PgkvYrul3bntTz/l6uXnhQ2bvP+M7NqTcayh681tSqPaA5cJCUBp/vrP7vpPRpej4Eeyx2qz5p0tE/2N2g=="],
|
||||
|
||||
"@algolia/client-personalization": ["@algolia/client-personalization@5.49.1", "", { "dependencies": { "@algolia/client-common": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", "@algolia/requester-fetch": "5.49.1", "@algolia/requester-node-http": "5.49.1" } }, "sha512-v+4DN+lkYfBd01Hbnb9ZrCHe7l+mvihyx218INRX/kaCXROIWUDIT1cs3urQxfE7kXBFnLsqYeOflQALv/gA5w=="],
|
||||
|
||||
"@algolia/client-query-suggestions": ["@algolia/client-query-suggestions@5.49.1", "", { "dependencies": { "@algolia/client-common": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", "@algolia/requester-fetch": "5.49.1", "@algolia/requester-node-http": "5.49.1" } }, "sha512-Un11cab6ZCv0W+Jiak8UktGIqoa4+gSNgEZNfG8m8eTsXGqwIEr370H3Rqwj87zeNSlFpH2BslMXJ/cLNS1qtg=="],
|
||||
|
||||
"@algolia/client-search": ["@algolia/client-search@5.49.1", "", { "dependencies": { "@algolia/client-common": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", "@algolia/requester-fetch": "5.49.1", "@algolia/requester-node-http": "5.49.1" } }, "sha512-Nt9hri7nbOo0RipAsGjIssHkpLMHHN/P7QqENywAq5TLsoYDzUyJGny8FEiD/9KJUxtGH8blGpMedilI6kK3rA=="],
|
||||
|
||||
"@algolia/ingestion": ["@algolia/ingestion@1.49.1", "", { "dependencies": { "@algolia/client-common": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", "@algolia/requester-fetch": "5.49.1", "@algolia/requester-node-http": "5.49.1" } }, "sha512-b5hUXwDqje0Y4CpU6VL481DXgPgxpTD5sYMnfQTHKgUispGnaCLCm2/T9WbJo1YNUbX3iHtYDArp804eD6CmRQ=="],
|
||||
|
||||
"@algolia/monitoring": ["@algolia/monitoring@1.49.1", "", { "dependencies": { "@algolia/client-common": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", "@algolia/requester-fetch": "5.49.1", "@algolia/requester-node-http": "5.49.1" } }, "sha512-bvrXwZ0WsL3rN6Q4m4QqxsXFCo6WAew7sAdrpMQMK4Efn4/W920r9ptOuckejOSSvyLr9pAWgC5rsHhR2FYuYw=="],
|
||||
|
||||
"@algolia/recommend": ["@algolia/recommend@5.49.1", "", { "dependencies": { "@algolia/client-common": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", "@algolia/requester-fetch": "5.49.1", "@algolia/requester-node-http": "5.49.1" } }, "sha512-h2yz3AGeGkQwNgbLmoe3bxYs8fac4An1CprKTypYyTU/k3Q+9FbIvJ8aS1DoBKaTjSRZVoyQS7SZQio6GaHbZw=="],
|
||||
|
||||
"@algolia/requester-browser-xhr": ["@algolia/requester-browser-xhr@5.49.1", "", { "dependencies": { "@algolia/client-common": "5.49.1" } }, "sha512-2UPyRuUR/qpqSqH8mxFV5uBZWEpxhGPHLlx9Xf6OVxr79XO2ctzZQAhsmTZ6X22x+N8MBWpB9UEky7YU2HGFgA=="],
|
||||
|
||||
"@algolia/requester-fetch": ["@algolia/requester-fetch@5.49.1", "", { "dependencies": { "@algolia/client-common": "5.49.1" } }, "sha512-N+xlE4lN+wpuT+4vhNEwPVlrfN+DWAZmSX9SYhbz986Oq8AMsqdntOqUyiOXVxYsQtfLwmiej24vbvJGYv1Qtw=="],
|
||||
|
||||
"@algolia/requester-node-http": ["@algolia/requester-node-http@5.49.1", "", { "dependencies": { "@algolia/client-common": "5.49.1" } }, "sha512-zA5bkUOB5PPtTr182DJmajCiizHp0rCJQ0Chf96zNFvkdESKYlDeYA3tQ7r2oyHbu/8DiohAQ5PZ85edctzbXA=="],
|
||||
|
||||
"@antfu/install-pkg": ["@antfu/install-pkg@1.1.0", "", { "dependencies": { "package-manager-detector": "^1.3.0", "tinyexec": "^1.0.1" } }, "sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ=="],
|
||||
|
||||
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
|
||||
|
||||
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
|
||||
|
||||
"@babel/parser": ["@babel/parser@7.29.0", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww=="],
|
||||
|
||||
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
|
||||
|
||||
"@braintree/sanitize-url": ["@braintree/sanitize-url@7.1.2", "", {}, "sha512-jigsZK+sMF/cuiB7sERuo9V7N9jx+dhmHHnQyDSVdpZwVutaBu7WvNYqMDLSgFgfB30n452TP3vjDAvFC973mA=="],
|
||||
|
||||
"@catppuccin/vitepress": ["@catppuccin/vitepress@0.1.2", "", { "peerDependencies": { "typescript": "^5.0.0" } }, "sha512-dqhgo6U6GWbgh3McAgwemUC8Y2Aj48rRcQx/9iuPzBPAgo7NA3yi7ZcR0wolAENMmoOMAHBV+rz/5DfiGxtZLA=="],
|
||||
|
||||
"@chevrotain/cst-dts-gen": ["@chevrotain/cst-dts-gen@11.1.2", "", { "dependencies": { "@chevrotain/gast": "11.1.2", "@chevrotain/types": "11.1.2", "lodash-es": "4.17.23" } }, "sha512-XTsjvDVB5nDZBQB8o0o/0ozNelQtn2KrUVteIHSlPd2VAV2utEb6JzyCJaJ8tGxACR4RiBNWy5uYUHX2eji88Q=="],
|
||||
|
||||
"@chevrotain/gast": ["@chevrotain/gast@11.1.2", "", { "dependencies": { "@chevrotain/types": "11.1.2", "lodash-es": "4.17.23" } }, "sha512-Z9zfXR5jNZb1Hlsd/p+4XWeUFugrHirq36bKzPWDSIacV+GPSVXdk+ahVWZTwjhNwofAWg/sZg58fyucKSQx5g=="],
|
||||
|
||||
"@chevrotain/regexp-to-ast": ["@chevrotain/regexp-to-ast@11.1.2", "", {}, "sha512-nMU3Uj8naWer7xpZTYJdxbAs6RIv/dxYzkYU8GSwgUtcAAlzjcPfX1w+RKRcYG8POlzMeayOQ/znfwxEGo5ulw=="],
|
||||
|
||||
"@chevrotain/types": ["@chevrotain/types@11.1.2", "", {}, "sha512-U+HFai5+zmJCkK86QsaJtoITlboZHBqrVketcO2ROv865xfCMSFpELQoz1GkX5GzME8pTa+3kbKrZHQtI0gdbw=="],
|
||||
|
||||
"@chevrotain/utils": ["@chevrotain/utils@11.1.2", "", {}, "sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA=="],
|
||||
|
||||
"@docsearch/css": ["@docsearch/css@3.8.2", "", {}, "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ=="],
|
||||
|
||||
"@docsearch/js": ["@docsearch/js@3.8.2", "", { "dependencies": { "@docsearch/react": "3.8.2", "preact": "^10.0.0" } }, "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ=="],
|
||||
|
||||
"@docsearch/react": ["@docsearch/react@3.8.2", "", { "dependencies": { "@algolia/autocomplete-core": "1.17.7", "@algolia/autocomplete-preset-algolia": "1.17.7", "@docsearch/css": "3.8.2", "algoliasearch": "^5.14.2" }, "peerDependencies": { "@types/react": ">= 16.8.0 < 19.0.0", "react": ">= 16.8.0 < 19.0.0", "react-dom": ">= 16.8.0 < 19.0.0", "search-insights": ">= 1 < 3" }, "optionalPeers": ["@types/react", "react", "react-dom", "search-insights"] }, "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg=="],
|
||||
|
||||
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.21.5", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ=="],
|
||||
|
||||
"@esbuild/android-arm": ["@esbuild/android-arm@0.21.5", "", { "os": "android", "cpu": "arm" }, "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg=="],
|
||||
|
||||
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.21.5", "", { "os": "android", "cpu": "arm64" }, "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A=="],
|
||||
|
||||
"@esbuild/android-x64": ["@esbuild/android-x64@0.21.5", "", { "os": "android", "cpu": "x64" }, "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA=="],
|
||||
|
||||
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.21.5", "", { "os": "darwin", "cpu": "arm64" }, "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ=="],
|
||||
|
||||
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.21.5", "", { "os": "darwin", "cpu": "x64" }, "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw=="],
|
||||
|
||||
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.21.5", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g=="],
|
||||
|
||||
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.21.5", "", { "os": "freebsd", "cpu": "x64" }, "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ=="],
|
||||
|
||||
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.21.5", "", { "os": "linux", "cpu": "arm" }, "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA=="],
|
||||
|
||||
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.21.5", "", { "os": "linux", "cpu": "arm64" }, "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q=="],
|
||||
|
||||
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.21.5", "", { "os": "linux", "cpu": "ia32" }, "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg=="],
|
||||
|
||||
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg=="],
|
||||
|
||||
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg=="],
|
||||
|
||||
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.21.5", "", { "os": "linux", "cpu": "ppc64" }, "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w=="],
|
||||
|
||||
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.21.5", "", { "os": "linux", "cpu": "none" }, "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA=="],
|
||||
|
||||
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.21.5", "", { "os": "linux", "cpu": "s390x" }, "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A=="],
|
||||
|
||||
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.21.5", "", { "os": "linux", "cpu": "x64" }, "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ=="],
|
||||
|
||||
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.21.5", "", { "os": "none", "cpu": "x64" }, "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg=="],
|
||||
|
||||
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.21.5", "", { "os": "openbsd", "cpu": "x64" }, "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow=="],
|
||||
|
||||
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.21.5", "", { "os": "sunos", "cpu": "x64" }, "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg=="],
|
||||
|
||||
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.21.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A=="],
|
||||
|
||||
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.21.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA=="],
|
||||
|
||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.21.5", "", { "os": "win32", "cpu": "x64" }, "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw=="],
|
||||
|
||||
"@fontsource/jetbrains-mono": ["@fontsource/jetbrains-mono@5.2.8", "", {}, "sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ=="],
|
||||
|
||||
"@fontsource/manrope": ["@fontsource/manrope@5.2.8", "", {}, "sha512-gJHJmcuUk7qWcNCfcAri/DJQtXtBYqi9yKratr4jXhSo0I3xUtNNKI+igQIcw5c+m95g0vounk8ZnX/kb8o0TA=="],
|
||||
|
||||
"@iconify-json/simple-icons": ["@iconify-json/simple-icons@1.2.72", "", { "dependencies": { "@iconify/types": "*" } }, "sha512-wkcixntHvaCoqPqerGrNFcHQ3Yx1ux4ZkhscCDK0DEHpP62XCH+cxq1HTsRjbUiQl/M9K8bj03HF6Wgn5iE2rQ=="],
|
||||
|
||||
"@iconify/types": ["@iconify/types@2.0.0", "", {}, "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg=="],
|
||||
|
||||
"@iconify/utils": ["@iconify/utils@3.1.0", "", { "dependencies": { "@antfu/install-pkg": "^1.1.0", "@iconify/types": "^2.0.0", "mlly": "^1.8.0" } }, "sha512-Zlzem1ZXhI1iHeeERabLNzBHdOa4VhQbqAcOQaMKuTuyZCpwKbC2R4Dd0Zo3g9EAc+Y4fiarO8HIHRAth7+skw=="],
|
||||
|
||||
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
|
||||
|
||||
"@mermaid-js/parser": ["@mermaid-js/parser@1.0.0", "", { "dependencies": { "langium": "^4.0.0" } }, "sha512-vvK0Hi/VWndxoh03Mmz6wa1KDriSPjS2XMZL/1l19HFwygiObEEoEwSDxOqyLzzAI6J2PU3261JjTMTO7x+BPw=="],
|
||||
|
||||
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.59.0", "", { "os": "android", "cpu": "arm" }, "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg=="],
|
||||
|
||||
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.59.0", "", { "os": "android", "cpu": "arm64" }, "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q=="],
|
||||
|
||||
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.59.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg=="],
|
||||
|
||||
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.59.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w=="],
|
||||
|
||||
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.59.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA=="],
|
||||
|
||||
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.59.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw=="],
|
||||
|
||||
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.59.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA=="],
|
||||
|
||||
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.59.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg=="],
|
||||
|
||||
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA=="],
|
||||
|
||||
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.59.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg=="],
|
||||
|
||||
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.59.0", "", { "os": "linux", "cpu": "none" }, "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg=="],
|
||||
|
||||
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.59.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg=="],
|
||||
|
||||
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.59.0", "", { "os": "linux", "cpu": "x64" }, "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg=="],
|
||||
|
||||
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.59.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ=="],
|
||||
|
||||
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.59.0", "", { "os": "none", "cpu": "arm64" }, "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA=="],
|
||||
|
||||
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.59.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A=="],
|
||||
|
||||
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.59.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA=="],
|
||||
|
||||
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.59.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA=="],
|
||||
|
||||
"@shikijs/core": ["@shikijs/core@2.5.0", "", { "dependencies": { "@shikijs/engine-javascript": "2.5.0", "@shikijs/engine-oniguruma": "2.5.0", "@shikijs/types": "2.5.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.4" } }, "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg=="],
|
||||
|
||||
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@2.5.0", "", { "dependencies": { "@shikijs/types": "2.5.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^3.1.0" } }, "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w=="],
|
||||
|
||||
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@2.5.0", "", { "dependencies": { "@shikijs/types": "2.5.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw=="],
|
||||
|
||||
"@shikijs/langs": ["@shikijs/langs@2.5.0", "", { "dependencies": { "@shikijs/types": "2.5.0" } }, "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w=="],
|
||||
|
||||
"@shikijs/themes": ["@shikijs/themes@2.5.0", "", { "dependencies": { "@shikijs/types": "2.5.0" } }, "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw=="],
|
||||
|
||||
"@shikijs/transformers": ["@shikijs/transformers@2.5.0", "", { "dependencies": { "@shikijs/core": "2.5.0", "@shikijs/types": "2.5.0" } }, "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg=="],
|
||||
|
||||
"@shikijs/types": ["@shikijs/types@2.5.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw=="],
|
||||
|
||||
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
|
||||
|
||||
"@types/d3": ["@types/d3@7.4.3", "", { "dependencies": { "@types/d3-array": "*", "@types/d3-axis": "*", "@types/d3-brush": "*", "@types/d3-chord": "*", "@types/d3-color": "*", "@types/d3-contour": "*", "@types/d3-delaunay": "*", "@types/d3-dispatch": "*", "@types/d3-drag": "*", "@types/d3-dsv": "*", "@types/d3-ease": "*", "@types/d3-fetch": "*", "@types/d3-force": "*", "@types/d3-format": "*", "@types/d3-geo": "*", "@types/d3-hierarchy": "*", "@types/d3-interpolate": "*", "@types/d3-path": "*", "@types/d3-polygon": "*", "@types/d3-quadtree": "*", "@types/d3-random": "*", "@types/d3-scale": "*", "@types/d3-scale-chromatic": "*", "@types/d3-selection": "*", "@types/d3-shape": "*", "@types/d3-time": "*", "@types/d3-time-format": "*", "@types/d3-timer": "*", "@types/d3-transition": "*", "@types/d3-zoom": "*" } }, "sha512-lZXZ9ckh5R8uiFVt8ogUNf+pIrK4EsWrx2Np75WvF/eTpJ0FMHNhjXk8CKEx/+gpHbNQyJWehbFaTvqmHWB3ww=="],
|
||||
|
||||
"@types/d3-array": ["@types/d3-array@3.2.2", "", {}, "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw=="],
|
||||
|
||||
"@types/d3-axis": ["@types/d3-axis@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-pYeijfZuBd87T0hGn0FO1vQ/cgLk6E1ALJjfkC0oJ8cbwkZl3TpgS8bVBLZN+2jjGgg38epgxb2zmoGtSfvgMw=="],
|
||||
|
||||
"@types/d3-brush": ["@types/d3-brush@3.0.6", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-nH60IZNNxEcrh6L1ZSMNA28rj27ut/2ZmI3r96Zd+1jrZD++zD3LsMIjWlvg4AYrHn/Pqz4CF3veCxGjtbqt7A=="],
|
||||
|
||||
"@types/d3-chord": ["@types/d3-chord@3.0.6", "", {}, "sha512-LFYWWd8nwfwEmTZG9PfQxd17HbNPksHBiJHaKuY1XeqscXacsS2tyoo6OdRsjf+NQYeB6XrNL3a25E3gH69lcg=="],
|
||||
|
||||
"@types/d3-color": ["@types/d3-color@3.1.3", "", {}, "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A=="],
|
||||
|
||||
"@types/d3-contour": ["@types/d3-contour@3.0.6", "", { "dependencies": { "@types/d3-array": "*", "@types/geojson": "*" } }, "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg=="],
|
||||
|
||||
"@types/d3-delaunay": ["@types/d3-delaunay@6.0.4", "", {}, "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw=="],
|
||||
|
||||
"@types/d3-dispatch": ["@types/d3-dispatch@3.0.7", "", {}, "sha512-5o9OIAdKkhN1QItV2oqaE5KMIiXAvDWBDPrD85e58Qlz1c1kI/J0NcqbEG88CoTwJrYe7ntUCVfeUl2UJKbWgA=="],
|
||||
|
||||
"@types/d3-drag": ["@types/d3-drag@3.0.7", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-HE3jVKlzU9AaMazNufooRJ5ZpWmLIoc90A37WU2JMmeq28w1FQqCZswHZ3xR+SuxYftzHq6WU6KJHvqxKzTxxQ=="],
|
||||
|
||||
"@types/d3-dsv": ["@types/d3-dsv@3.0.7", "", {}, "sha512-n6QBF9/+XASqcKK6waudgL0pf/S5XHPPI8APyMLLUHd8NqouBGLsU8MgtO7NINGtPBtk9Kko/W4ea0oAspwh9g=="],
|
||||
|
||||
"@types/d3-ease": ["@types/d3-ease@3.0.2", "", {}, "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA=="],
|
||||
|
||||
"@types/d3-fetch": ["@types/d3-fetch@3.0.7", "", { "dependencies": { "@types/d3-dsv": "*" } }, "sha512-fTAfNmxSb9SOWNB9IoG5c8Hg6R+AzUHDRlsXsDZsNp6sxAEOP0tkP3gKkNSO/qmHPoBFTxNrjDprVHDQDvo5aA=="],
|
||||
|
||||
"@types/d3-force": ["@types/d3-force@3.0.10", "", {}, "sha512-ZYeSaCF3p73RdOKcjj+swRlZfnYpK1EbaDiYICEEp5Q6sUiqFaFQ9qgoshp5CzIyyb/yD09kD9o2zEltCexlgw=="],
|
||||
|
||||
"@types/d3-format": ["@types/d3-format@3.0.4", "", {}, "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g=="],
|
||||
|
||||
"@types/d3-geo": ["@types/d3-geo@3.1.0", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-856sckF0oP/diXtS4jNsiQw/UuK5fQG8l/a9VVLeSouf1/PPbBE1i1W852zVwKwYCBkFJJB7nCFTbk6UMEXBOQ=="],
|
||||
|
||||
"@types/d3-hierarchy": ["@types/d3-hierarchy@3.1.7", "", {}, "sha512-tJFtNoYBtRtkNysX1Xq4sxtjK8YgoWUNpIiUee0/jHGRwqvzYxkq0hGVbbOGSz+JgFxxRu4K8nb3YpG3CMARtg=="],
|
||||
|
||||
"@types/d3-interpolate": ["@types/d3-interpolate@3.0.4", "", { "dependencies": { "@types/d3-color": "*" } }, "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA=="],
|
||||
|
||||
"@types/d3-path": ["@types/d3-path@3.1.1", "", {}, "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="],
|
||||
|
||||
"@types/d3-polygon": ["@types/d3-polygon@3.0.2", "", {}, "sha512-ZuWOtMaHCkN9xoeEMr1ubW2nGWsp4nIql+OPQRstu4ypeZ+zk3YKqQT0CXVe/PYqrKpZAi+J9mTs05TKwjXSRA=="],
|
||||
|
||||
"@types/d3-quadtree": ["@types/d3-quadtree@3.0.6", "", {}, "sha512-oUzyO1/Zm6rsxKRHA1vH0NEDG58HrT5icx/azi9MF1TWdtttWl0UIUsjEQBBh+SIkrpd21ZjEv7ptxWys1ncsg=="],
|
||||
|
||||
"@types/d3-random": ["@types/d3-random@3.0.3", "", {}, "sha512-Imagg1vJ3y76Y2ea0871wpabqp613+8/r0mCLEBfdtqC7xMSfj9idOnmBYyMoULfHePJyxMAw3nWhJxzc+LFwQ=="],
|
||||
|
||||
"@types/d3-scale": ["@types/d3-scale@4.0.9", "", { "dependencies": { "@types/d3-time": "*" } }, "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw=="],
|
||||
|
||||
"@types/d3-scale-chromatic": ["@types/d3-scale-chromatic@3.1.0", "", {}, "sha512-iWMJgwkK7yTRmWqRB5plb1kadXyQ5Sj8V/zYlFGMUBbIPKQScw+Dku9cAAMgJG+z5GYDoMjWGLVOvjghDEFnKQ=="],
|
||||
|
||||
"@types/d3-selection": ["@types/d3-selection@3.0.11", "", {}, "sha512-bhAXu23DJWsrI45xafYpkQ4NtcKMwWnAC/vKrd2l+nxMFuvOT3XMYTIj2opv8vq8AO5Yh7Qac/nSeP/3zjTK0w=="],
|
||||
|
||||
"@types/d3-shape": ["@types/d3-shape@3.1.8", "", { "dependencies": { "@types/d3-path": "*" } }, "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w=="],
|
||||
|
||||
"@types/d3-time": ["@types/d3-time@3.0.4", "", {}, "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="],
|
||||
|
||||
"@types/d3-time-format": ["@types/d3-time-format@4.0.3", "", {}, "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg=="],
|
||||
|
||||
"@types/d3-timer": ["@types/d3-timer@3.0.2", "", {}, "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="],
|
||||
|
||||
"@types/d3-transition": ["@types/d3-transition@3.0.9", "", { "dependencies": { "@types/d3-selection": "*" } }, "sha512-uZS5shfxzO3rGlu0cC3bjmMFKsXv+SmZZcgp0KD22ts4uGXp5EVYGzu/0YdwZeKmddhcAccYtREJKkPfXkZuCg=="],
|
||||
|
||||
"@types/d3-zoom": ["@types/d3-zoom@3.0.8", "", { "dependencies": { "@types/d3-interpolate": "*", "@types/d3-selection": "*" } }, "sha512-iqMC4/YlFCSlO8+2Ii1GGGliCAY4XdeG748w5vQUbevlbDu0zSjH/+jojorQVBK/se0j6DUFNPBGSqD3YWYnDw=="],
|
||||
|
||||
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
|
||||
|
||||
"@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
|
||||
|
||||
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
|
||||
|
||||
"@types/linkify-it": ["@types/linkify-it@5.0.0", "", {}, "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q=="],
|
||||
|
||||
"@types/markdown-it": ["@types/markdown-it@14.1.2", "", { "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog=="],
|
||||
|
||||
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
|
||||
|
||||
"@types/mdurl": ["@types/mdurl@2.0.0", "", {}, "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg=="],
|
||||
|
||||
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
|
||||
|
||||
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
|
||||
|
||||
"@types/web-bluetooth": ["@types/web-bluetooth@0.0.21", "", {}, "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA=="],
|
||||
|
||||
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
|
||||
|
||||
"@vitejs/plugin-vue": ["@vitejs/plugin-vue@5.2.4", "", { "peerDependencies": { "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA=="],
|
||||
|
||||
"@vue/compiler-core": ["@vue/compiler-core@3.5.29", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/shared": "3.5.29", "entities": "^7.0.1", "estree-walker": "^2.0.2", "source-map-js": "^1.2.1" } }, "sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw=="],
|
||||
|
||||
"@vue/compiler-dom": ["@vue/compiler-dom@3.5.29", "", { "dependencies": { "@vue/compiler-core": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg=="],
|
||||
|
||||
"@vue/compiler-sfc": ["@vue/compiler-sfc@3.5.29", "", { "dependencies": { "@babel/parser": "^7.29.0", "@vue/compiler-core": "3.5.29", "@vue/compiler-dom": "3.5.29", "@vue/compiler-ssr": "3.5.29", "@vue/shared": "3.5.29", "estree-walker": "^2.0.2", "magic-string": "^0.30.21", "postcss": "^8.5.6", "source-map-js": "^1.2.1" } }, "sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA=="],
|
||||
|
||||
"@vue/compiler-ssr": ["@vue/compiler-ssr@3.5.29", "", { "dependencies": { "@vue/compiler-dom": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw=="],
|
||||
|
||||
"@vue/devtools-api": ["@vue/devtools-api@7.7.9", "", { "dependencies": { "@vue/devtools-kit": "^7.7.9" } }, "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g=="],
|
||||
|
||||
"@vue/devtools-kit": ["@vue/devtools-kit@7.7.9", "", { "dependencies": { "@vue/devtools-shared": "^7.7.9", "birpc": "^2.3.0", "hookable": "^5.5.3", "mitt": "^3.0.1", "perfect-debounce": "^1.0.0", "speakingurl": "^14.0.1", "superjson": "^2.2.2" } }, "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA=="],
|
||||
|
||||
"@vue/devtools-shared": ["@vue/devtools-shared@7.7.9", "", { "dependencies": { "rfdc": "^1.4.1" } }, "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA=="],
|
||||
|
||||
"@vue/reactivity": ["@vue/reactivity@3.5.29", "", { "dependencies": { "@vue/shared": "3.5.29" } }, "sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA=="],
|
||||
|
||||
"@vue/runtime-core": ["@vue/runtime-core@3.5.29", "", { "dependencies": { "@vue/reactivity": "3.5.29", "@vue/shared": "3.5.29" } }, "sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg=="],
|
||||
|
||||
"@vue/runtime-dom": ["@vue/runtime-dom@3.5.29", "", { "dependencies": { "@vue/reactivity": "3.5.29", "@vue/runtime-core": "3.5.29", "@vue/shared": "3.5.29", "csstype": "^3.2.3" } }, "sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg=="],
|
||||
|
||||
"@vue/server-renderer": ["@vue/server-renderer@3.5.29", "", { "dependencies": { "@vue/compiler-ssr": "3.5.29", "@vue/shared": "3.5.29" }, "peerDependencies": { "vue": "3.5.29" } }, "sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g=="],
|
||||
|
||||
"@vue/shared": ["@vue/shared@3.5.29", "", {}, "sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg=="],
|
||||
|
||||
"@vueuse/core": ["@vueuse/core@12.8.2", "", { "dependencies": { "@types/web-bluetooth": "^0.0.21", "@vueuse/metadata": "12.8.2", "@vueuse/shared": "12.8.2", "vue": "^3.5.13" } }, "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ=="],
|
||||
|
||||
"@vueuse/integrations": ["@vueuse/integrations@12.8.2", "", { "dependencies": { "@vueuse/core": "12.8.2", "@vueuse/shared": "12.8.2", "vue": "^3.5.13" }, "peerDependencies": { "async-validator": "^4", "axios": "^1", "change-case": "^5", "drauu": "^0.4", "focus-trap": "^7", "fuse.js": "^7", "idb-keyval": "^6", "jwt-decode": "^4", "nprogress": "^0.2", "qrcode": "^1.5", "sortablejs": "^1", "universal-cookie": "^7" }, "optionalPeers": ["async-validator", "axios", "change-case", "drauu", "focus-trap", "fuse.js", "idb-keyval", "jwt-decode", "nprogress", "qrcode", "sortablejs", "universal-cookie"] }, "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g=="],
|
||||
|
||||
"@vueuse/metadata": ["@vueuse/metadata@12.8.2", "", {}, "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A=="],
|
||||
|
||||
"@vueuse/shared": ["@vueuse/shared@12.8.2", "", { "dependencies": { "vue": "^3.5.13" } }, "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w=="],
|
||||
|
||||
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
|
||||
|
||||
"algoliasearch": ["algoliasearch@5.49.1", "", { "dependencies": { "@algolia/abtesting": "1.15.1", "@algolia/client-abtesting": "5.49.1", "@algolia/client-analytics": "5.49.1", "@algolia/client-common": "5.49.1", "@algolia/client-insights": "5.49.1", "@algolia/client-personalization": "5.49.1", "@algolia/client-query-suggestions": "5.49.1", "@algolia/client-search": "5.49.1", "@algolia/ingestion": "1.49.1", "@algolia/monitoring": "1.49.1", "@algolia/recommend": "5.49.1", "@algolia/requester-browser-xhr": "5.49.1", "@algolia/requester-fetch": "5.49.1", "@algolia/requester-node-http": "5.49.1" } }, "sha512-X3Pp2aRQhg4xUC6PQtkubn5NpRKuUPQ9FPDQlx36SmpFwwH2N0/tw4c+NXV3nw3PsgeUs+BuWGP0gjz3TvENLQ=="],
|
||||
|
||||
"birpc": ["birpc@2.9.0", "", {}, "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw=="],
|
||||
|
||||
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
|
||||
|
||||
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
|
||||
|
||||
"character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
|
||||
|
||||
"chevrotain": ["chevrotain@11.1.2", "", { "dependencies": { "@chevrotain/cst-dts-gen": "11.1.2", "@chevrotain/gast": "11.1.2", "@chevrotain/regexp-to-ast": "11.1.2", "@chevrotain/types": "11.1.2", "@chevrotain/utils": "11.1.2", "lodash-es": "4.17.23" } }, "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg=="],
|
||||
|
||||
"chevrotain-allstar": ["chevrotain-allstar@0.3.1", "", { "dependencies": { "lodash-es": "^4.17.21" }, "peerDependencies": { "chevrotain": "^11.0.0" } }, "sha512-b7g+y9A0v4mxCW1qUhf3BSVPg+/NvGErk/dOkrDaHA0nQIQGAtrOjlX//9OQtRlSCy+x9rfB5N8yC71lH1nvMw=="],
|
||||
|
||||
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
|
||||
|
||||
"commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="],
|
||||
|
||||
"confbox": ["confbox@0.1.8", "", {}, "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w=="],
|
||||
|
||||
"copy-anything": ["copy-anything@4.0.5", "", { "dependencies": { "is-what": "^5.2.0" } }, "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA=="],
|
||||
|
||||
"cose-base": ["cose-base@1.0.3", "", { "dependencies": { "layout-base": "^1.0.0" } }, "sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg=="],
|
||||
|
||||
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||
|
||||
"cytoscape": ["cytoscape@3.33.1", "", {}, "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ=="],
|
||||
|
||||
"cytoscape-cose-bilkent": ["cytoscape-cose-bilkent@4.1.0", "", { "dependencies": { "cose-base": "^1.0.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-wgQlVIUJF13Quxiv5e1gstZ08rnZj2XaLHGoFMYXz7SkNfCDOOteKBE6SYRfA9WxxI/iBc3ajfDoc6hb/MRAHQ=="],
|
||||
|
||||
"cytoscape-fcose": ["cytoscape-fcose@2.2.0", "", { "dependencies": { "cose-base": "^2.2.0" }, "peerDependencies": { "cytoscape": "^3.2.0" } }, "sha512-ki1/VuRIHFCzxWNrsshHYPs6L7TvLu3DL+TyIGEsRcvVERmxokbf5Gdk7mFxZnTdiGtnA4cfSmjZJMviqSuZrQ=="],
|
||||
|
||||
"d3": ["d3@7.9.0", "", { "dependencies": { "d3-array": "3", "d3-axis": "3", "d3-brush": "3", "d3-chord": "3", "d3-color": "3", "d3-contour": "4", "d3-delaunay": "6", "d3-dispatch": "3", "d3-drag": "3", "d3-dsv": "3", "d3-ease": "3", "d3-fetch": "3", "d3-force": "3", "d3-format": "3", "d3-geo": "3", "d3-hierarchy": "3", "d3-interpolate": "3", "d3-path": "3", "d3-polygon": "3", "d3-quadtree": "3", "d3-random": "3", "d3-scale": "4", "d3-scale-chromatic": "3", "d3-selection": "3", "d3-shape": "3", "d3-time": "3", "d3-time-format": "4", "d3-timer": "3", "d3-transition": "3", "d3-zoom": "3" } }, "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA=="],
|
||||
|
||||
"d3-array": ["d3-array@3.2.4", "", { "dependencies": { "internmap": "1 - 2" } }, "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg=="],
|
||||
|
||||
"d3-axis": ["d3-axis@3.0.0", "", {}, "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw=="],
|
||||
|
||||
"d3-brush": ["d3-brush@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "3", "d3-transition": "3" } }, "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ=="],
|
||||
|
||||
"d3-chord": ["d3-chord@3.0.1", "", { "dependencies": { "d3-path": "1 - 3" } }, "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g=="],
|
||||
|
||||
"d3-color": ["d3-color@3.1.0", "", {}, "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA=="],
|
||||
|
||||
"d3-contour": ["d3-contour@4.0.2", "", { "dependencies": { "d3-array": "^3.2.0" } }, "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA=="],
|
||||
|
||||
"d3-delaunay": ["d3-delaunay@6.0.4", "", { "dependencies": { "delaunator": "5" } }, "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A=="],
|
||||
|
||||
"d3-dispatch": ["d3-dispatch@3.0.1", "", {}, "sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg=="],
|
||||
|
||||
"d3-drag": ["d3-drag@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-selection": "3" } }, "sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg=="],
|
||||
|
||||
"d3-dsv": ["d3-dsv@3.0.1", "", { "dependencies": { "commander": "7", "iconv-lite": "0.6", "rw": "1" }, "bin": { "csv2json": "bin/dsv2json.js", "csv2tsv": "bin/dsv2dsv.js", "dsv2dsv": "bin/dsv2dsv.js", "dsv2json": "bin/dsv2json.js", "json2csv": "bin/json2dsv.js", "json2dsv": "bin/json2dsv.js", "json2tsv": "bin/json2dsv.js", "tsv2csv": "bin/dsv2dsv.js", "tsv2json": "bin/dsv2json.js" } }, "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q=="],
|
||||
|
||||
"d3-ease": ["d3-ease@3.0.1", "", {}, "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w=="],
|
||||
|
||||
"d3-fetch": ["d3-fetch@3.0.1", "", { "dependencies": { "d3-dsv": "1 - 3" } }, "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw=="],
|
||||
|
||||
"d3-force": ["d3-force@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-quadtree": "1 - 3", "d3-timer": "1 - 3" } }, "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg=="],
|
||||
|
||||
"d3-format": ["d3-format@3.1.2", "", {}, "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg=="],
|
||||
|
||||
"d3-geo": ["d3-geo@3.1.1", "", { "dependencies": { "d3-array": "2.5.0 - 3" } }, "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q=="],
|
||||
|
||||
"d3-hierarchy": ["d3-hierarchy@3.1.2", "", {}, "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA=="],
|
||||
|
||||
"d3-interpolate": ["d3-interpolate@3.0.1", "", { "dependencies": { "d3-color": "1 - 3" } }, "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g=="],
|
||||
|
||||
"d3-path": ["d3-path@3.1.0", "", {}, "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ=="],
|
||||
|
||||
"d3-polygon": ["d3-polygon@3.0.1", "", {}, "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg=="],
|
||||
|
||||
"d3-quadtree": ["d3-quadtree@3.0.1", "", {}, "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw=="],
|
||||
|
||||
"d3-random": ["d3-random@3.0.1", "", {}, "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ=="],
|
||||
|
||||
"d3-sankey": ["d3-sankey@0.12.3", "", { "dependencies": { "d3-array": "1 - 2", "d3-shape": "^1.2.0" } }, "sha512-nQhsBRmM19Ax5xEIPLMY9ZmJ/cDvd1BG3UVvt5h3WRxKg5zGRbvnteTyWAbzeSvlh3tW7ZEmq4VwR5mB3tutmQ=="],
|
||||
|
||||
"d3-scale": ["d3-scale@4.0.2", "", { "dependencies": { "d3-array": "2.10.0 - 3", "d3-format": "1 - 3", "d3-interpolate": "1.2.0 - 3", "d3-time": "2.1.1 - 3", "d3-time-format": "2 - 4" } }, "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ=="],
|
||||
|
||||
"d3-scale-chromatic": ["d3-scale-chromatic@3.1.0", "", { "dependencies": { "d3-color": "1 - 3", "d3-interpolate": "1 - 3" } }, "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ=="],
|
||||
|
||||
"d3-selection": ["d3-selection@3.0.0", "", {}, "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ=="],
|
||||
|
||||
"d3-shape": ["d3-shape@3.2.0", "", { "dependencies": { "d3-path": "^3.1.0" } }, "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA=="],
|
||||
|
||||
"d3-time": ["d3-time@3.1.0", "", { "dependencies": { "d3-array": "2 - 3" } }, "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q=="],
|
||||
|
||||
"d3-time-format": ["d3-time-format@4.1.0", "", { "dependencies": { "d3-time": "1 - 3" } }, "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg=="],
|
||||
|
||||
"d3-timer": ["d3-timer@3.0.1", "", {}, "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA=="],
|
||||
|
||||
"d3-transition": ["d3-transition@3.0.1", "", { "dependencies": { "d3-color": "1 - 3", "d3-dispatch": "1 - 3", "d3-ease": "1 - 3", "d3-interpolate": "1 - 3", "d3-timer": "1 - 3" }, "peerDependencies": { "d3-selection": "2 - 3" } }, "sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w=="],
|
||||
|
||||
"d3-zoom": ["d3-zoom@3.0.0", "", { "dependencies": { "d3-dispatch": "1 - 3", "d3-drag": "2 - 3", "d3-interpolate": "1 - 3", "d3-selection": "2 - 3", "d3-transition": "2 - 3" } }, "sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw=="],
|
||||
|
||||
"dagre-d3-es": ["dagre-d3-es@7.0.13", "", { "dependencies": { "d3": "^7.9.0", "lodash-es": "^4.17.21" } }, "sha512-efEhnxpSuwpYOKRm/L5KbqoZmNNukHa/Flty4Wp62JRvgH2ojwVgPgdYyr4twpieZnyRDdIH7PY2mopX26+j2Q=="],
|
||||
|
||||
"dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="],
|
||||
|
||||
"delaunator": ["delaunator@5.0.1", "", { "dependencies": { "robust-predicates": "^3.0.2" } }, "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw=="],
|
||||
|
||||
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
|
||||
|
||||
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
|
||||
|
||||
"dompurify": ["dompurify@3.3.2", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ=="],
|
||||
|
||||
"emoji-regex-xs": ["emoji-regex-xs@1.0.0", "", {}, "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg=="],
|
||||
|
||||
"entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||
|
||||
"esbuild": ["esbuild@0.21.5", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.21.5", "@esbuild/android-arm": "0.21.5", "@esbuild/android-arm64": "0.21.5", "@esbuild/android-x64": "0.21.5", "@esbuild/darwin-arm64": "0.21.5", "@esbuild/darwin-x64": "0.21.5", "@esbuild/freebsd-arm64": "0.21.5", "@esbuild/freebsd-x64": "0.21.5", "@esbuild/linux-arm": "0.21.5", "@esbuild/linux-arm64": "0.21.5", "@esbuild/linux-ia32": "0.21.5", "@esbuild/linux-loong64": "0.21.5", "@esbuild/linux-mips64el": "0.21.5", "@esbuild/linux-ppc64": "0.21.5", "@esbuild/linux-riscv64": "0.21.5", "@esbuild/linux-s390x": "0.21.5", "@esbuild/linux-x64": "0.21.5", "@esbuild/netbsd-x64": "0.21.5", "@esbuild/openbsd-x64": "0.21.5", "@esbuild/sunos-x64": "0.21.5", "@esbuild/win32-arm64": "0.21.5", "@esbuild/win32-ia32": "0.21.5", "@esbuild/win32-x64": "0.21.5" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw=="],
|
||||
|
||||
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
|
||||
|
||||
"focus-trap": ["focus-trap@7.8.0", "", { "dependencies": { "tabbable": "^6.4.0" } }, "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA=="],
|
||||
|
||||
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
|
||||
|
||||
"hachure-fill": ["hachure-fill@0.5.2", "", {}, "sha512-3GKBOn+m2LX9iq+JC1064cSFprJY4jL1jCXTcpnfER5HYE2l/4EfWSGzkPa/ZDBmYI0ZOEj5VHV/eKnPGkHuOg=="],
|
||||
|
||||
"hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
|
||||
|
||||
"hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
|
||||
|
||||
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||
|
||||
"html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
|
||||
|
||||
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
|
||||
|
||||
"internmap": ["internmap@1.0.1", "", {}, "sha512-lDB5YccMydFBtasVtxnZ3MRBHuaoE8GKsppq+EchKL2U4nK/DmEpPHNH8MZe5HkMtpSiTSOZwfN0tzYjO/lJEw=="],
|
||||
|
||||
"is-what": ["is-what@5.5.0", "", {}, "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw=="],
|
||||
|
||||
"katex": ["katex@0.16.35", "", { "dependencies": { "commander": "^8.3.0" }, "bin": { "katex": "cli.js" } }, "sha512-S0+riEvy1CK4VKse1ivMff8gmabe/prY7sKB3njjhyoLLsNFDQYtKNgXrbWUggGDCJBz7Fctl5i8fLCESHXzSg=="],
|
||||
|
||||
"khroma": ["khroma@2.1.0", "", {}, "sha512-Ls993zuzfayK269Svk9hzpeGUKob/sIgZzyHYdjQoAdQetRKpOLj+k/QQQ/6Qi0Yz65mlROrfd+Ev+1+7dz9Kw=="],
|
||||
|
||||
"langium": ["langium@4.2.1", "", { "dependencies": { "chevrotain": "~11.1.1", "chevrotain-allstar": "~0.3.1", "vscode-languageserver": "~9.0.1", "vscode-languageserver-textdocument": "~1.0.11", "vscode-uri": "~3.1.0" } }, "sha512-zu9QWmjpzJcomzdJQAHgDVhLGq5bLosVak1KVa40NzQHXfqr4eAHupvnPOVXEoLkg6Ocefvf/93d//SB7du4YQ=="],
|
||||
|
||||
"layout-base": ["layout-base@1.0.2", "", {}, "sha512-8h2oVEZNktL4BH2JCOI90iD1yXwL6iNW7KcCKT2QZgQJR2vbqDsldCTPRU9NifTCqHZci57XvQQ15YTu+sTYPg=="],
|
||||
|
||||
"lodash-es": ["lodash-es@4.17.23", "", {}, "sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg=="],
|
||||
|
||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||
|
||||
"mark.js": ["mark.js@8.11.1", "", {}, "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ=="],
|
||||
|
||||
"marked": ["marked@16.4.2", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA=="],
|
||||
|
||||
"mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="],
|
||||
|
||||
"mermaid": ["mermaid@11.12.3", "", { "dependencies": { "@braintree/sanitize-url": "^7.1.1", "@iconify/utils": "^3.0.1", "@mermaid-js/parser": "^1.0.0", "@types/d3": "^7.4.3", "cytoscape": "^3.29.3", "cytoscape-cose-bilkent": "^4.1.0", "cytoscape-fcose": "^2.2.0", "d3": "^7.9.0", "d3-sankey": "^0.12.3", "dagre-d3-es": "7.0.13", "dayjs": "^1.11.18", "dompurify": "^3.2.5", "katex": "^0.16.22", "khroma": "^2.1.0", "lodash-es": "^4.17.23", "marked": "^16.2.1", "roughjs": "^4.6.6", "stylis": "^4.3.6", "ts-dedent": "^2.2.0", "uuid": "^11.1.0" } }, "sha512-wN5ZSgJQIC+CHJut9xaKWsknLxaFBwCPwPkGTSUYrTiHORWvpT8RxGk849HPnpUAQ+/9BPRqYb80jTpearrHzQ=="],
|
||||
|
||||
"micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
|
||||
|
||||
"micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
|
||||
|
||||
"micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
|
||||
|
||||
"micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
|
||||
|
||||
"micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
|
||||
|
||||
"minisearch": ["minisearch@7.2.0", "", {}, "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg=="],
|
||||
|
||||
"mitt": ["mitt@3.0.1", "", {}, "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw=="],
|
||||
|
||||
"mlly": ["mlly@1.8.1", "", { "dependencies": { "acorn": "^8.16.0", "pathe": "^2.0.3", "pkg-types": "^1.3.1", "ufo": "^1.6.3" } }, "sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ=="],
|
||||
|
||||
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
|
||||
|
||||
"oniguruma-to-es": ["oniguruma-to-es@3.1.1", "", { "dependencies": { "emoji-regex-xs": "^1.0.0", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ=="],
|
||||
|
||||
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
|
||||
|
||||
"path-data-parser": ["path-data-parser@0.1.0", "", {}, "sha512-NOnmBpt5Y2RWbuv0LMzsayp3lVylAHLPUTut412ZA3l+C4uw4ZVkQbjShYCQ8TCpUMdPapr4YjUqLYD6v68j+w=="],
|
||||
|
||||
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
|
||||
|
||||
"perfect-debounce": ["perfect-debounce@1.0.0", "", {}, "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA=="],
|
||||
|
||||
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
|
||||
|
||||
"pkg-types": ["pkg-types@1.3.1", "", { "dependencies": { "confbox": "^0.1.8", "mlly": "^1.7.4", "pathe": "^2.0.1" } }, "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ=="],
|
||||
|
||||
"points-on-curve": ["points-on-curve@0.2.0", "", {}, "sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A=="],
|
||||
|
||||
"points-on-path": ["points-on-path@0.2.1", "", { "dependencies": { "path-data-parser": "0.1.0", "points-on-curve": "0.2.0" } }, "sha512-25ClnWWuw7JbWZcgqY/gJ4FQWadKxGWk+3kR/7kD0tCaDtPPMj7oHu2ToLaVhfpnHrZzYby2w6tUA0eOIuUg8g=="],
|
||||
|
||||
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
|
||||
|
||||
"preact": ["preact@10.28.4", "", {}, "sha512-uKFfOHWuSNpRFVTnljsCluEFq57OKT+0QdOiQo8XWnQ/pSvg7OpX5eNOejELXJMWy+BwM2nobz0FkvzmnpCNsQ=="],
|
||||
|
||||
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
|
||||
|
||||
"regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
|
||||
|
||||
"regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="],
|
||||
|
||||
"regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="],
|
||||
|
||||
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
|
||||
|
||||
"robust-predicates": ["robust-predicates@3.0.2", "", {}, "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="],
|
||||
|
||||
"rollup": ["rollup@4.59.0", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.59.0", "@rollup/rollup-android-arm64": "4.59.0", "@rollup/rollup-darwin-arm64": "4.59.0", "@rollup/rollup-darwin-x64": "4.59.0", "@rollup/rollup-freebsd-arm64": "4.59.0", "@rollup/rollup-freebsd-x64": "4.59.0", "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", "@rollup/rollup-linux-arm-musleabihf": "4.59.0", "@rollup/rollup-linux-arm64-gnu": "4.59.0", "@rollup/rollup-linux-arm64-musl": "4.59.0", "@rollup/rollup-linux-loong64-gnu": "4.59.0", "@rollup/rollup-linux-loong64-musl": "4.59.0", "@rollup/rollup-linux-ppc64-gnu": "4.59.0", "@rollup/rollup-linux-ppc64-musl": "4.59.0", "@rollup/rollup-linux-riscv64-gnu": "4.59.0", "@rollup/rollup-linux-riscv64-musl": "4.59.0", "@rollup/rollup-linux-s390x-gnu": "4.59.0", "@rollup/rollup-linux-x64-gnu": "4.59.0", "@rollup/rollup-linux-x64-musl": "4.59.0", "@rollup/rollup-openbsd-x64": "4.59.0", "@rollup/rollup-openharmony-arm64": "4.59.0", "@rollup/rollup-win32-arm64-msvc": "4.59.0", "@rollup/rollup-win32-ia32-msvc": "4.59.0", "@rollup/rollup-win32-x64-gnu": "4.59.0", "@rollup/rollup-win32-x64-msvc": "4.59.0", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg=="],
|
||||
|
||||
"roughjs": ["roughjs@4.6.6", "", { "dependencies": { "hachure-fill": "^0.5.2", "path-data-parser": "^0.1.0", "points-on-curve": "^0.2.0", "points-on-path": "^0.2.1" } }, "sha512-ZUz/69+SYpFN/g/lUlo2FXcIjRkSu3nDarreVdGGndHEBJ6cXPdKguS8JGxwj5HA5xIbVKSmLgr5b3AWxtRfvQ=="],
|
||||
|
||||
"rw": ["rw@1.3.3", "", {}, "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ=="],
|
||||
|
||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||
|
||||
"search-insights": ["search-insights@2.17.3", "", {}, "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ=="],
|
||||
|
||||
"shiki": ["shiki@2.5.0", "", { "dependencies": { "@shikijs/core": "2.5.0", "@shikijs/engine-javascript": "2.5.0", "@shikijs/engine-oniguruma": "2.5.0", "@shikijs/langs": "2.5.0", "@shikijs/themes": "2.5.0", "@shikijs/types": "2.5.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ=="],
|
||||
|
||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||
|
||||
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
|
||||
|
||||
"speakingurl": ["speakingurl@14.0.1", "", {}, "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ=="],
|
||||
|
||||
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
|
||||
|
||||
"stylis": ["stylis@4.3.6", "", {}, "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ=="],
|
||||
|
||||
"superjson": ["superjson@2.2.6", "", { "dependencies": { "copy-anything": "^4" } }, "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA=="],
|
||||
|
||||
"tabbable": ["tabbable@6.4.0", "", {}, "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg=="],
|
||||
|
||||
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="],
|
||||
|
||||
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
|
||||
|
||||
"ts-dedent": ["ts-dedent@2.2.0", "", {}, "sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ=="],
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
|
||||
|
||||
"unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
|
||||
|
||||
"unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
|
||||
|
||||
"unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
|
||||
|
||||
"unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="],
|
||||
|
||||
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
|
||||
|
||||
"uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
|
||||
|
||||
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
|
||||
|
||||
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
|
||||
|
||||
"vite": ["vite@5.4.21", "", { "dependencies": { "esbuild": "^0.21.3", "postcss": "^8.4.43", "rollup": "^4.20.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" }, "optionalPeers": ["@types/node", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser"], "bin": { "vite": "bin/vite.js" } }, "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw=="],
|
||||
|
||||
"vitepress": ["vitepress@1.6.4", "", { "dependencies": { "@docsearch/css": "3.8.2", "@docsearch/js": "3.8.2", "@iconify-json/simple-icons": "^1.2.21", "@shikijs/core": "^2.1.0", "@shikijs/transformers": "^2.1.0", "@shikijs/types": "^2.1.0", "@types/markdown-it": "^14.1.2", "@vitejs/plugin-vue": "^5.2.1", "@vue/devtools-api": "^7.7.0", "@vue/shared": "^3.5.13", "@vueuse/core": "^12.4.0", "@vueuse/integrations": "^12.4.0", "focus-trap": "^7.6.4", "mark.js": "8.11.1", "minisearch": "^7.1.1", "shiki": "^2.1.0", "vite": "^5.4.14", "vue": "^3.5.13" }, "peerDependencies": { "markdown-it-mathjax3": "^4", "postcss": "^8" }, "optionalPeers": ["markdown-it-mathjax3", "postcss"], "bin": { "vitepress": "bin/vitepress.js" } }, "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg=="],
|
||||
|
||||
"vscode-jsonrpc": ["vscode-jsonrpc@8.2.0", "", {}, "sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA=="],
|
||||
|
||||
"vscode-languageserver": ["vscode-languageserver@9.0.1", "", { "dependencies": { "vscode-languageserver-protocol": "3.17.5" }, "bin": { "installServerIntoExtension": "bin/installServerIntoExtension" } }, "sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g=="],
|
||||
|
||||
"vscode-languageserver-protocol": ["vscode-languageserver-protocol@3.17.5", "", { "dependencies": { "vscode-jsonrpc": "8.2.0", "vscode-languageserver-types": "3.17.5" } }, "sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg=="],
|
||||
|
||||
"vscode-languageserver-textdocument": ["vscode-languageserver-textdocument@1.0.12", "", {}, "sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA=="],
|
||||
|
||||
"vscode-languageserver-types": ["vscode-languageserver-types@3.17.5", "", {}, "sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg=="],
|
||||
|
||||
"vscode-uri": ["vscode-uri@3.1.0", "", {}, "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ=="],
|
||||
|
||||
"vue": ["vue@3.5.29", "", { "dependencies": { "@vue/compiler-dom": "3.5.29", "@vue/compiler-sfc": "3.5.29", "@vue/runtime-dom": "3.5.29", "@vue/server-renderer": "3.5.29", "@vue/shared": "3.5.29" }, "peerDependencies": { "typescript": "*" }, "optionalPeers": ["typescript"] }, "sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA=="],
|
||||
|
||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||
|
||||
"cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="],
|
||||
|
||||
"d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="],
|
||||
|
||||
"d3-sankey/d3-array": ["d3-array@2.12.1", "", { "dependencies": { "internmap": "^1.0.0" } }, "sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ=="],
|
||||
|
||||
"d3-sankey/d3-shape": ["d3-shape@1.3.7", "", { "dependencies": { "d3-path": "1" } }, "sha512-EUkvKjqPFUAZyOlhY5gzCxCeI0Aep04LwIRpsZ/mLFelJiUfnK56jo5JMDSE7yyP2kLSb6LtF+S5chMk7uqPqw=="],
|
||||
|
||||
"cytoscape-fcose/cose-base/layout-base": ["layout-base@2.0.1", "", {}, "sha512-dp3s92+uNI1hWIpPGH3jK2kxE2lMjdXdr+DH8ynZHpd6PUlH6x6cbuXnoMmiNumznqaNO31xu9e79F0uuZ0JFg=="],
|
||||
|
||||
"d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="],
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,574 @@
|
||||
# Changelog
|
||||
|
||||
## v0.14.0 (2026-05-12)
|
||||
|
||||
SubMiner no longer requires a globally-installed mpv plugin. The bundled plugin is injected at runtime only when SubMiner launches mpv — through the `subminer` launcher, the app's managed launch, or the packaged Windows SubMiner mpv shortcut. When you open mpv on its own, SubMiner is not involved and the plugin is never loaded. If you have a legacy global SubMiner plugin under mpv's `scripts` directory, first-run setup detects it and prompts you to remove it before playback starts.
|
||||
|
||||
**Added**
|
||||
|
||||
- **Character Dictionary:** Added AniList-based character dictionary selection for resolving title mismatches — open it in-app with the new `Ctrl+Alt+A` shortcut or from the CLI with `subminer dictionary --candidates` / `--select`. Series-scoped overrides replace stale entries in the merged dictionary.
|
||||
- **Primary Subtitle Bar:** Added a `V` shortcut and mpv plugin binding to toggle the primary subtitle bar without affecting mpv's native subtitle visibility.
|
||||
- **Texthooker:** Added `subminer texthooker -o` and a tray menu item to open the local texthooker page in the default browser.
|
||||
|
||||
**Changed**
|
||||
|
||||
- **mpv Plugin Setup:** SubMiner-managed launches now automatically inject the bundled mpv plugin when no global plugin is installed. The legacy plugin removal prompt appears before mpv starts so users can trash the old global plugin and relaunch immediately.
|
||||
- **Tray Menu:** Replaced the "Open Overlay" menu item with "Open Help", which opens the session help modal.
|
||||
- **Stats Exclusions:** Vocabulary exclusions now persist in the immersion database and import any existing browser-local exclusions on first load.
|
||||
- **Config Example:** The generated example config now lists every built-in default keybinding, with regression coverage ensuring they compile, dispatch in the overlay, and register through the mpv plugin.
|
||||
- **Default Startup Options:** Texthooker startup and subtitle/annotation WebSocket servers are now disabled by default. Fresh installs also get updated primary subtitle style defaults: a Japanese font stack, transparent subtitle backgrounds, stronger text shadows, and teal N4/fourth-band coloring.
|
||||
|
||||
**Fixed**
|
||||
|
||||
- **JLPT Underlines:** Restored persistent JLPT underlines on subtitle tokens; they now keep their JLPT color after dictionary lookups and when a token also carries a known-word or frequency annotation color.
|
||||
- **Hyprland Fullscreen Overlay:** Fixed fullscreen transitions on Hyprland so mpv fullscreen changes refresh overlay geometry, reassert topmost stacking, and keep hover-pause working through resize/toggle cycles. The overlay now aligns exactly with the mpv window, disables floating-window decoration on exact placement, and no longer pins across workspaces.
|
||||
- **macOS Overlay:** Kept the overlay visible and interactive during transient tracker refreshes and while mpv is the active tracked window. Fixed z-order so the overlay stays above mpv but behind other foreground windows.
|
||||
- **Subtitle Annotation Colors:** Fixed annotated token colors so `subtitleStyle` typography is preserved and higher-priority known-word and frequency colors correctly take precedence over JLPT colors. Added a subtle brightness lift for hover states so transparent hover backgrounds still show a visible affordance. Hid the browser focus ring on the overlay surface so focused overlays no longer show a viewport border.
|
||||
- **Grammar and Particle Annotation Filters:** Suppressed annotation styling (N+1, JLPT, frequency, name) for grammar-only tokens including auxiliary inflection fragments like `れる`/`れた`, polite copula tails like `です`/`ですよ`, negative copula phrases like `じゃないですか`, and standalone particles like `に`; lexical forms like `くれる` remain eligible.
|
||||
- **Interjection and Existence Verb Filters:** Suppressed annotations for ハァ-style interjections and `ある`/`有る` existence verbs; known-word highlighting still applies for `ある`.
|
||||
- **Known-Word Compound Tokens:** Preserved Yomitan compound token boundaries for known-word highlighting so known component words no longer color a larger unknown compound green.
|
||||
- **Kana Token Annotations:** Stopped kana-only tokens from being selected as N+1 targets or receiving N+1, JLPT, or frequency annotation metadata.
|
||||
- **Subtitle Bar Modes:** Changed `v` to cycle the primary subtitle bar through visible, hover, and hidden modes with OSD feedback. A new `subtitleStyle.primaryDefaultMode` config option sets the startup default independently of secondary subtitles.
|
||||
- **Subtitle Keybindings:** Fixed the default replay/next subtitle keybindings by moving session help to `Ctrl/Cmd+/`, freeing `Ctrl+Shift+H` and `Ctrl+Shift+L` for subtitle playback controls. The mpv plugin now registers shifted letter chords correctly so `Ctrl+Shift+L` reaches play-next-subtitle; play-next also starts playback from a paused state before re-pausing at subtitle end.
|
||||
- **Yomitan Popup Precedence:** Fixed keyboard-only Yomitan popup controls so popup shortcuts take precedence over overlay keybindings like `j`.
|
||||
- **Subtitle Prefetch:** Kept annotation prefetch running after immediate cache-hit renders so upcoming subtitle colors stay ready.
|
||||
- **Frequency Highlighting:** Fixed frequency highlighting for ordinal prefix-noun tokens like `第二` so JPDB ranks are preserved in subtitle annotations.
|
||||
- **Known-Word Refresh After Mining:** Refreshed the current subtitle after successful card mining so newly known words recolor immediately.
|
||||
- **Linux Multi-Line Copy:** Fixed multi-line subtitle copy on Linux so the overlay handles the follow-up digit selection locally, eliminating the timeout.
|
||||
- **mpv Crash Notifications:** Stopped mpv from owning long-running SubMiner subprocesses during shutdown, preventing desktop crash notifications when closing video.
|
||||
- **mpv Buffering Reload:** Kept the overlay alive across same-media reloads during buffering, avoiding duplicate startup gates and AniSkip lookups.
|
||||
- **mpv Playlist Navigation:** Playlist navigation now reuses the running overlay without repeating the pause-until-ready warmup gate.
|
||||
- **Anki Manual Updates:** Manual clipboard subtitle updates now replace sentence audio and media even when audio overwrite is disabled, while preserving existing word audio.
|
||||
- **AniList Progress Tracking:** Fixed post-watch progress to check on mpv time-position updates using the fresh mpv position, wired manual mark-watched to force a progress sync, and filled missing episode metadata from the filename parser. Prevented duplicate post-watch writes during concurrent checks and preserved manual watched marks when sync fails.
|
||||
- **AniList Setup:** Prevented config reload from opening the setup window during playback when token storage is unavailable; stopped setup from reporting success when encrypted token persistence fails.
|
||||
- **AniList Linux Keyring:** Retried keyring availability after transient GNOME libsecret startup failures so stored tokens load once the keyring becomes available.
|
||||
- **Stats Daemon:** Fixed stats background mode to route through the isolated stats daemon and deferred in-app stats startup when a daemon is already running, so video launches no longer fail when the stats port is in use.
|
||||
- **Stats Session Detail:** Fixed recent session detail pages showing "Media not found" before lifetime media summaries are available.
|
||||
- **Hover Background Config:** Accepted `subtitleStyle.hoverBackground` as an alias for `subtitleStyle.hoverTokenBackgroundColor` so setting it to `transparent` removes hover token backgrounds.
|
||||
- **Managed Playback Exit:** Launcher-managed playback now exits the background SubMiner app when the video closes; explicit background launches remain persistent.
|
||||
- **Multi-Mine Shortcuts:** Multi-line subtitle mining now accepts follow-up number-row digits even when the original shortcut modifiers are still held.
|
||||
- **Jellyfin Setup:** Improved setup with recent server selection and inline authentication feedback; added a tray "Jellyfin Discovery" toggle for runtime-only cast discovery.
|
||||
- **Subtitle POS Filtering:** Used Yomitan `wordClasses` metadata for subtitle part-of-speech filtering and backfilled blank MeCab POS detail fields during parser enrichment.
|
||||
|
||||
**Docs**
|
||||
|
||||
- Improved the docs homepage with canonical URLs and a cleaner sitemap for better search engine indexing.
|
||||
|
||||
<details>
|
||||
<summary>Internal changes</summary>
|
||||
|
||||
**Internal**
|
||||
|
||||
- Replaced the changelog renderer with an AI polish pass that merges related fragments and writes user-facing release notes. `CHANGELOG.md` keeps internal items in a collapsed `<details>` block; GitHub release notes omit them entirely.
|
||||
- Release CI no longer auto-builds pending `changes/*.md` fragments on tag. Tagging now fails fast if fragments remain — run `bun run changelog:build` (requires the `claude` CLI) and commit before tagging.
|
||||
|
||||
</details>
|
||||
|
||||
## Previous Versions
|
||||
|
||||
<details>
|
||||
<summary>v0.12.x</summary>
|
||||
|
||||
<h2>v0.12.0 (2026-04-11)</h2>
|
||||
|
||||
**Changed**
|
||||
|
||||
- Overlay: Added configurable overlay shortcuts for session help, controller select, and controller debug actions.
|
||||
- Overlay: Added mpv/plugin and CLI routing for session help, controller utilities, and subtitle sidebar toggling through the shared session-action path.
|
||||
- Overlay: Improved dedicated overlay modal retry and focus handling for runtime options, Jimaku, session help, controller tools, and the playlist browser.
|
||||
- Overlay: Fixed controller configuration and controller debug shortcut opens so configured bindings bring up their modals again instead of tripping renderer recovery.
|
||||
- Stats: Sessions are rolled up per episode within each day, with a bulk delete that wipes every session in the group.
|
||||
- Stats: Trends add a 365-day range next to the existing 7d/30d/90d/all options.
|
||||
- Stats: Library detail view gets a delete-episode action that removes the video and all its sessions.
|
||||
- Stats: Vocabulary Top 50 tightens the word/reading column so katakana entries no longer push the scores off screen.
|
||||
- Stats: Episode detail hides card events whose Anki notes have been deleted, instead of showing phantom mining activity.
|
||||
- Stats: Trend and watch-time charts share a unified theme with horizontal gridlines and larger ticks for legibility.
|
||||
- Stats: Overview, Library, Trends, Sessions, and Vocabulary now use generic "title" wording so YouTube videos and anime live comfortably side by side in the dashboard.
|
||||
- Stats: Session timeline no longer plots seek-forward/seek-backward markers — they were too noisy on sessions with lots of rewinds.
|
||||
- Stats: Replaced the "Library — Per Day" section on the Stats → Trends page with a "Library — Summary" section. The new section shows a top-10 watch-time leaderboard chart and a sortable per-title table (watch time, videos, sessions, cards, words, lookups, lookups/100w, date range), all scoped to the current date range selector.
|
||||
|
||||
**Fixed**
|
||||
|
||||
- Overlay: Fixed overlay drag-and-drop routing so dropping external subtitle files like `.ass` onto mpv still loads them when the overlay is visible.
|
||||
- Overlay: Addressed the latest CodeRabbit follow-ups on PR #49, including generation-scoped Lua session binding names, stricter session command validation, session-help shortcut visibility, the numeric-selection key guard, stats-overlay startup classification, and safer session-binding persistence.
|
||||
- Overlay: Addressed the latest CodeRabbit follow-ups on the Windows overlay flow, including exact mpv target resolution, lower-overlay helper arguments, Win32 failure detection, and overlay cleanup on tracker loss.
|
||||
- Overlay: Fixed Windows overlay z-order so the visible subtitle overlay stops staying above unrelated apps after mpv loses focus.
|
||||
- Overlay: Fixed Windows overlay tracking to use native window polling and owner/z-order binding, which keeps the subtitle overlay aligned to the active mpv window more reliably.
|
||||
- Overlay: Fixed Windows overlay hide/restore behavior so minimizing mpv immediately hides the overlay and restoring mpv brings it back on top of the mpv window without requiring a click.
|
||||
- Overlay: Fixed stats overlay layering so the in-player stats page now stays above mpv and the subtitle overlay while it is open.
|
||||
- Overlay: Fixed Windows subtitle overlay stability so transient tracker misses and restore events keep the current subtitle visible instead of waiting for the next subtitle line.
|
||||
- Overlay: Fixed Windows focus handoff from the interactive subtitle overlay back to mpv so the overlay no longer drops behind mpv and briefly disappears.
|
||||
- Overlay: Fixed Windows visible-overlay startup so it no longer briefly opens as an interactive or opaque surface before the tracked transparent overlay state settles.
|
||||
- Overlay: Fixed spurious auto-pause after overlay visibility recovery and window resize so the overlay no longer pauses mpv until the pointer genuinely re-enters the subtitle area.
|
||||
- Overlay: Fixed Windows secondary subtitle hover mode so the expanded hover hit area no longer blocks the native minimize, maximize, and close buttons.
|
||||
- Overlay: Fixed Windows Yomitan popup focus loss after closing nested lookups so the original popup stays interactive instead of falling through to mpv.
|
||||
- Stats: Fixed immersion-tracker timestamp handling under Bun/libsql so library rows, session timelines, and lifetime summaries keep real wall-clock millisecond values instead of truncating to invalid negative timestamps.
|
||||
- Mpv Plugin: Fixed the mpv Lua plugin so hover and environment modules no longer use the `goto continue` pattern that can fail to parse on some user Lua runtimes.
|
||||
|
||||
**Internal**
|
||||
|
||||
- Release: Added a dedicated beta/rc prerelease GitHub Actions workflow that publishes GitHub prereleases without consuming pending changelog fragments or updating AUR.
|
||||
- Release: Added prerelease note generation so beta and release-candidate tags can reuse the current pending `changes/*.md` fragments while leaving stable changelog publication for the final release cut.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.11.x</summary>
|
||||
|
||||
<h2>v0.11.2 (2026-04-07)</h2>
|
||||
|
||||
**Changed**
|
||||
|
||||
- Launcher: Replaced the launcher-only fullscreen toggle with `mpv.launchMode` so SubMiner-managed mpv playback can start in normal, maximized, or fullscreen mode.
|
||||
|
||||
**Fixed**
|
||||
|
||||
- Launcher: Fixed launcher-managed mpv spawning to force an explicit X11 GPU path when Wayland trackers are unavailable.
|
||||
- Launcher: Local playback now promotes a single unlabeled external subtitle sidecar to the primary slot instead of leaving mpv's embedded English auto-selection in place.
|
||||
- Release: Fixed Linux AppImage startup packaging so Chromium child relaunches can resolve the bundled `libffmpeg.so` instead of crash-looping on startup.
|
||||
|
||||
<h2>v0.11.1 (2026-04-04)</h2>
|
||||
|
||||
**Fixed**
|
||||
|
||||
- Release: Linux packaged builds now expose the canonical `SubMiner` app identity to Electron's startup metadata so native Wayland compositors stop reporting the window class/app-id as lowercase `subminer`.
|
||||
- Linux: Linux now restores the runtime options, Jimaku, and Subsync shortcuts after the Electron 39 regression by routing those actions through the overlay's mpv/IPC shortcut path.
|
||||
|
||||
<h2>v0.11.0 (2026-04-03)</h2>
|
||||
|
||||
**Added**
|
||||
|
||||
- Overlay: Added a playlist browser overlay modal for browsing sibling video files and the live mpv queue during playback.
|
||||
- Overlay: Added the default `Ctrl+Alt+P` keybinding to open the playlist browser and manage queue order without leaving playback.
|
||||
|
||||
**Changed**
|
||||
|
||||
- Setup: Made mpv plugin installation mandatory in the first-run setup flow, removed the skip path, and kept Finish disabled until the plugin is installed.
|
||||
- Setup: Clarified that the mpv plugin requirement applies to setup on every platform, while the optional `SubMiner mpv` shortcut remains the recommended Windows playback entry point.
|
||||
- Launcher: Streamlined Windows setup and config by making the `SubMiner mpv` shortcut self-contained and keeping `mpv.executablePath` as the simple fallback when `mpv.exe` is not on `PATH`.
|
||||
- Overlay: Changed fresh-install default config to keep texthooker and stats from auto-opening browser tabs.
|
||||
- Overlay: Changed fresh-install default config to enable AnkiConnect, Discord Rich Presence, subtitle-sidebar, and Yomitan-popup auto-pause by default, while disabling controller input by default.
|
||||
|
||||
**Fixed**
|
||||
|
||||
- Main: Resolve the YouTube playback socket path lazily so startup honors CLI and config overrides.
|
||||
- Main: Add regression coverage for the lazy socket-path lookup during Windows mpv startup.
|
||||
- Main: Keep integrated `--start --texthooker` launches on the full app-ready startup path so the texthooker page and websocket servers start together during normal playback startup.
|
||||
- Main: Stop the mpv/plugin auto-start flow from spawning a separate standalone texthooker helper during normal `subminer <video>` launches.
|
||||
- Overlay: Keep tracked macOS visible overlays click-through by default so subtitle sidebar passthrough works immediately without requiring a subtitle hover cycle first.
|
||||
- Overlay: Add regression coverage for the macOS visible-overlay passthrough default.
|
||||
- Anilist: Stop AniList post-watch from sending a second progress update when the current episode was already satisfied by a ready retry item in the same watch-completion pass.
|
||||
- Anilist: Add regression coverage for the retry-queue plus live-update duplicate path.
|
||||
- Overlay: Fixed Kiku duplicate grouping to reuse duplicate note IDs from both generic sentence-card creation and Yomitan popup mining instead of running extra duplicate scans after add.
|
||||
- Overlay: Fixed the Yomitan popup mining flow to add cards in the background while keeping the stock popup progress feedback, then pause playback and close the lookup popup before the Kiku merge modal opens.
|
||||
- Overlay: Fixed configured subtitle-jump keybindings so backward and forward subtitle seeks keep playback paused when invoked from a paused state.
|
||||
- Launcher: Fixed the Windows `SubMiner mpv` shortcut and `SubMiner.exe --launch-mpv` flow to launch mpv with SubMiner's required default args directly instead of requiring an `mpv.conf` profile named `subminer`.
|
||||
- Launcher: Clarified the Windows install and usage docs so the shortcut path is documented as self-contained, while the optional `subminer` mpv profile remains available for manual mpv launches.
|
||||
- Launcher: Hardened the first-run setup blocker copy and stale custom-scheme handling so setup messages stay aligned with config, plugin, and dictionary readiness.
|
||||
- Launcher: Fixed the Windows `SubMiner mpv` shortcut idle launch so loading a video after opening the shortcut keeps mpv in the expected SubMiner-managed session, auto-starts the overlay, and re-arms subtitle auto-selection for the newly opened file.
|
||||
- Launcher: Removed the redundant `.` subtitle search path from the Windows shortcut launch args and deduped repeated subtitle source tracks in the manual sync picker so duplicate external subtitle entries no longer appear from the shortcut path.
|
||||
- Playback: Fixed managed local playback so duplicate startup-ready retries no longer unpause media after a later manual pause on the same file.
|
||||
- Playback: Fixed managed local subtitle auto-selection so local files reuse configured primary and secondary subtitle language priorities instead of staying on mpv's initial `sid=auto` guess.
|
||||
- Launcher: Added a blank-by-default `mpv.executablePath` override for Windows playback so users can point SubMiner at `mpv.exe` when it is not on `PATH`.
|
||||
- Launcher: Kept the Windows shortcut and `--launch-mpv` flow simple by preserving PATH auto-discovery as the default and exposing the override in first-run setup.
|
||||
- Launcher: Added `windows` as a recognized launcher backend option and auto-detection target on Windows.
|
||||
- Launcher: Honored `SUBMINER_YTDLP_BIN` consistently across YouTube playback URL resolution, track probing, subtitle downloads, and metadata probing.
|
||||
- Launcher: Kept the first-run setup window from navigating away on unexpected URLs.
|
||||
- Launcher: Made Windows mpv honor an explicitly configured executable path instead of silently falling back to PATH.
|
||||
- Launcher: Hardened `--launch-mpv` parsing and Windows binary resolution so valueless flags do not swallow media targets and symlinked launcher installs do not short-circuit PATH lookup.
|
||||
- Launcher: Fixed first-run setup blocking playback on macOS when the SubMiner mpv plugin was already installed at the canonical `~/.config/mpv` path.
|
||||
- Launcher: Fixed setup gating so stale cancelled setup state no longer prevents playback when the canonical mpv plugin entrypoint already exists.
|
||||
- Playback: Prevented stale async playlist-browser subtitle rearm callbacks from overriding newer subtitle selections during rapid file changes.
|
||||
|
||||
**Docs**
|
||||
|
||||
- Docs Site: Added a dedicated Subtitle Sidebar guide and linked it from the homepage and configuration docs.
|
||||
- Docs Site: Linked Jimaku integration from the homepage to its dedicated docs page.
|
||||
- Docs Site: Refreshed docs-site theme tokens and hover/selection styling for the updated pages.
|
||||
|
||||
**Internal**
|
||||
|
||||
- Release: Retried AUR clone and push operations in the tagged release workflow.
|
||||
- Release: Kept GitHub Releases green when AUR publish flakes and needs manual follow-up.
|
||||
- Release: Updated Electron to 39.8.6 and pinned patched transitive build dependencies to clear the reported high-severity audit findings.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.10.x</summary>
|
||||
|
||||
<h2>v0.10.0 (2026-03-29)</h2>
|
||||
|
||||
**Changed**
|
||||
|
||||
- Integrations: Replaced the deprecated Discord Rich Presence wrapper with the maintained `@xhayper/discord-rpc` package.
|
||||
|
||||
**Fixed**
|
||||
|
||||
- Stats: Fixed stats startup so the immersion tracker can run when `Bun.serve` is unavailable.
|
||||
- Stats: Stats server now falls back to a Node `http` listener in Electron/runtime paths that do not expose Bun.
|
||||
- Overlay: Fixed the macOS visible-overlay toggle path so manual hides stay hidden and the plugin uses the explicit visible-overlay toggle command.
|
||||
- Subtitle Sidebar: Restored macOS mpv passthrough while the overlay subtitle sidebar is open so clicks outside the sidebar can refocus mpv and keep native keybindings working.
|
||||
|
||||
**Internal**
|
||||
|
||||
- Release: Added a maintained source coverage lane that shards Bun coverage one test file at a time and merges LCOV output into `coverage/test-src/lcov.info`.
|
||||
- Release: CI and release quality-gate now upload the merged source-lane LCOV artifact for inspection.
|
||||
- Runtime: Extracted remaining inline runtime logic from `src/main.ts` into dedicated runtime modules and composer helpers.
|
||||
- Runtime: Added focused regression tests for the extracted runtime/composer boundaries.
|
||||
- Runtime: Updated task tracking notes to mark TASK-238.6 complete and confirm follow-on boot-phase split can be deferred.
|
||||
- Runtime: Split `src/main.ts` boot wiring into dedicated `src/main/boot/services.ts`, `src/main/boot/runtimes.ts`, and `src/main/boot/handlers.ts` modules.
|
||||
- Runtime: Added focused tests for the new boot-phase seams and kept the startup/typecheck/build verification lanes green.
|
||||
- Runtime: Updated internal architecture/task docs to record the boot-phase split and new ownership boundary.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.9.x</summary>
|
||||
|
||||
<h2>v0.9.3 (2026-03-25)</h2>
|
||||
|
||||
**Changed**
|
||||
|
||||
- Launcher: Moved YouTube primary subtitle language defaults to `youtube.primarySubLanguages`.
|
||||
- Launcher: Removed the placeholder YouTube subtitle retime step and now uses downloaded primary subtitle tracks directly, so there is no fake path rewrite before playback/sidebar loading.
|
||||
- YouTube: Removed the `src/core/services/youtube/retime` helper and its tests after retiring the internal retime strategy.
|
||||
- Docs: Clarified optional `alass` / `ffsubsync` subtitle-sync requirements and setup steps, including fallback behavior when sync tools are absent.
|
||||
- Launcher: Removed the old `youtubeSubgen.primarySubLanguages` config path from the generated config and docs.
|
||||
|
||||
<h2>v0.9.2 (2026-03-25)</h2>
|
||||
|
||||
**Fixed**
|
||||
|
||||
- Overlay: Fixed overlay pointer tracking so Windows click-through toggles immediately when the cursor enters or leaves subtitle regions, without waiting for a later hover resync.
|
||||
- Overlay: Fixed Windows overlay window tracking on scaled displays by converting native tracked window bounds to Electron DIP coordinates before applying overlay bounds.
|
||||
- Launcher: Fixed Windows direct `--youtube-play` startup so MPV boots reliably, stays paused until the app-owned subtitle flow is ready, and reuses an already-running SubMiner instance when available.
|
||||
- Launcher: Fixed standalone Windows `--youtube-play` sessions so closing MPV fully exits SubMiner instead of leaving hidden overlay windows or a background process behind.
|
||||
- Overlay: Fixed `subminer <youtube-url>` on Linux so the YouTube playback flow waits for Yomitan to load before creating the overlay window, avoiding the broken lookup popup state that previously required a manual overlay refresh.
|
||||
|
||||
<h2>v0.9.1 (2026-03-24)</h2>
|
||||
|
||||
**Changed**
|
||||
|
||||
- Release: Reduced packaged release size by excluding duplicate `extraResources` payload and pruning docs, tests, sourcemaps, and other source-only files from Electron bundles.
|
||||
|
||||
**Fixed**
|
||||
|
||||
- Overlay: Restored controller navigation and lookup/mining controls while the subtitle sidebar is open, while keeping true modal dialogs blocking controller actions.
|
||||
- Tokenizer: Fixed subtitle annotation clearing so explanatory contrast endings like `んですけど` are excluded consistently across the shared tokenizer filter and annotation stage.
|
||||
|
||||
<h2>v0.9.0 (2026-03-23)</h2>
|
||||
|
||||
**Added**
|
||||
|
||||
- Docs: Added a new WebSocket / Texthooker API and integration guide covering WebSocket payloads, custom client patterns, mpv plugin automation, and webhook-style relay examples. Linked from configuration and mining workflow docs for easier discovery.
|
||||
|
||||
**Changed**
|
||||
|
||||
- Launcher: Added an app-owned YouTube subtitle flow that pauses mpv, uses absPlayer-style YouTube timedtext parsing/conversion to download subtitle tracks, and injects them as external files before playback resumes.
|
||||
- Launcher: Changed YouTube subtitle startup to auto-load the best-available primary and secondary subtitle tracks at launch instead of forcing the picker modal first. Secondary subtitle failures no longer block playback resume.
|
||||
- Launcher: Added `Ctrl+Alt+C` as the default keybinding to manually open the YouTube subtitle picker during active YouTube playback.
|
||||
- Launcher: Added yt-dlp metadata probing so YouTube playback and immersion tracking record canonical video title and channel metadata.
|
||||
- Launcher: Stopped forcing `--ytdl-raw-options=` before user-provided mpv options so existing YouTube cookie integrations in user `--args` are no longer clobbered.
|
||||
- Launcher: Disabled mpv native YouTube subtitle auto-loading for the app-owned flow so injected external subtitle files remain authoritative.
|
||||
- Launcher: Added OSD status messages for YouTube playback startup, subtitle acquisition, and subtitle loading so the flow stays visible before and during the picker.
|
||||
- Subtitle Sidebar: Added startup-auto-open controls and resume positioning improvements so the sidebar jumps directly to the first resolved active cue.
|
||||
- Subtitle Sidebar: Improved subtitle prefetch and embedded overlay passthrough sync so sidebar and overlay subtitle states stay consistent across media transitions.
|
||||
- Subtitle Sidebar: Updated scroll handling, embedded layout styling, and active-cue visual behavior.
|
||||
- Stats: Stats Library tab now displays YouTube video title, channel name, and channel thumbnail for YouTube media entries, with retry logic to fill in metadata that arrives after initial load.
|
||||
|
||||
**Fixed**
|
||||
|
||||
- Launcher: Fixed Anki media mining for mpv YouTube streams by unwrapping the stream URL so audio and screenshot capture work correctly for YouTube playback sessions.
|
||||
- Immersion: Fixed YouTube media path handling in the immersion runtime and tracking so YouTube sessions record correct media references, AniList guessing skips YouTube URLs, and post-watch state transitions do not fire for YouTube media.
|
||||
- Launcher: Fixed startup-launched YouTube playback so primary subtitle overlay updates continue after auto-load completes.
|
||||
- Launcher: Fixed auto-loaded YouTube primary subtitles so parsed cues appear in the subtitle sidebar without needing a manual picker retry.
|
||||
- Launcher: Fixed the YouTube picker to guard against duplicate subtitle submissions and tightened YouTube URL detection so follow-up runtime flows only treat real YouTube hosts as YouTube playback.
|
||||
- Launcher: Fixed primary subtitle failure notifications being shown while app-owned YouTube subtitle probing and downloads are still in flight.
|
||||
- Launcher: Preserved existing authoritative YouTube subtitle tracks when available; downloaded tracks are used only to fill missing sides, and native mpv secondary subtitle rendering is hidden so the overlay remains the sole secondary display.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.8.x</summary>
|
||||
|
||||
<h2>v0.8.0 (2026-03-22)</h2>
|
||||
|
||||
**Added**
|
||||
|
||||
- Overlay: Added the subtitle sidebar feature with a new `subtitleSidebar` configuration surface and rendered sidebar modal with cue list rendering, click-to-seek, active-cue highlighting, and embedded layout support.
|
||||
- IPC: Added sidebar snapshot plumbing between renderer and main process for overlay/sidebar synchronization.
|
||||
|
||||
**Changed**
|
||||
|
||||
- Config: Added hot-reloadable sidebar options for enablement, layout, visibility, typography, opacity, sizing, and interaction behavior (`autoOpen`, `pauseOnHover`, `autoScroll`, toggle key).
|
||||
- Docs: Added full `subtitleSidebar` documentation coverage, including sample config, option table, and toggle shortcut notes.
|
||||
- Runtime: Improved subtitle prefetch/rendering flow so sidebar and overlay subtitle states stay in sync across media transitions.
|
||||
|
||||
**Fixed**
|
||||
|
||||
- Overlay: Kept sidebar cue tracking stable across playback transitions and timing edge cases.
|
||||
- Overlay: Improved sidebar resume/start behavior to jump directly to the first resolved active cue.
|
||||
- Overlay: Stopped stale subtitle refreshes from regressing active-cue and text state.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.7.x</summary>
|
||||
|
||||
<h2>v0.7.0 (2026-03-19)</h2>
|
||||
|
||||
**Added**
|
||||
|
||||
- Immersion: Added Mine Word, Mine Sentence, and Mine Audio buttons to word detail example lines in the stats dashboard.
|
||||
- Immersion: Mine Word creates a full Yomitan card (definition, reading, pitch accent) via the hidden search page bridge, then enriches with sentence audio, screenshot, and metadata extracted from the source video.
|
||||
- Immersion: Mine Sentence and Mine Audio create cards directly with appropriate Lapis/Kiku flags, sentence highlighting, and media from the source file.
|
||||
- Immersion: Media generation (audio + image/AVIF) runs in parallel and respects all AnkiConnect config options.
|
||||
- Immersion: Added word exclusion list to the Vocabulary tab with localStorage persistence and a management modal.
|
||||
- Immersion: Fixed truncated readings in the frequency rank table (e.g. お前 now shows おまえ instead of まえ).
|
||||
- Immersion: Clicking a bar in the Top Repeated Words chart now opens the word detail panel.
|
||||
- Immersion: Secondary subtitle text is now stored alongside primary subtitle lines for use as translation when mining cards from the stats page.
|
||||
- Stats: Added `subminer stats -b` to start or reuse a dedicated background stats server without blocking normal SubMiner instances.
|
||||
- Stats: Added `subminer stats -s` to stop the dedicated background stats server without closing browser tabs.
|
||||
- Stats: Stats server startup now reuses a running background stats daemon instead of trying to bind a second local server in another SubMiner instance.
|
||||
- Launcher: Added launcher passthrough for `-a/--args` so mpv receives raw extra launch flags (`--fs`, `--ytdl-format`, custom audio/video settings, etc.) from the `subminer` command.
|
||||
- Launcher: Added `subminer stats` to launch the local stats dashboard, force-start the stats server on demand, and open the dashboard in your browser.
|
||||
- Launcher: Added `subminer stats cleanup` to backfill vocabulary metadata and prune stale or excluded immersion rows on demand.
|
||||
- Launcher: Added `stats.autoOpenBrowser` so browser launch after `subminer stats` can be enabled or disabled explicitly.
|
||||
- Immersion: Added a local stats dashboard for immersion tracking with Overview, Anime, Trends, Vocabulary, and Sessions views.
|
||||
- Immersion: Added anime progress, episode completion, Anki card links, and occurrence drill-down across the stats dashboard.
|
||||
- Immersion: Added richer session timelines with new-word activity, cumulative totals, and pause/seek/card event markers.
|
||||
- Immersion: Added completed-episodes and completed-anime totals to the Overview tracking snapshot.
|
||||
|
||||
**Changed**
|
||||
|
||||
- Anki: Changed known-word cache settings to live under `ankiConnect.knownWords` instead of mixing them into `ankiConnect.nPlusOne`.
|
||||
- Anki: Kept legacy `ankiConnect.nPlusOne` known-word keys and older `ankiConnect.behavior.nPlusOne*` keys as deprecated compatibility fallbacks.
|
||||
- Stats: Added session deletion to the Sessions tab with the same confirmation prompt used by anime episode/session deletes, and removed all associated session rows from the stats database.
|
||||
- Immersion: Kept immersion tracking history by default while preserving daily/monthly rollup maintenance.
|
||||
- Immersion: Added exact lifetime summary reads for overview/anime/media stats so dashboard totals no longer depend on rescanning raw telemetry.
|
||||
- Immersion: Reduced tracker storage overhead by removing duplicated subtitle text from subtitle-line event payloads.
|
||||
- Immersion: Deduplicated episode cover-art blobs through a shared blob store and updated cover-art reads/writes to resolve shared images correctly.
|
||||
- Immersion: Added indexes for large-history session, telemetry, vocabulary, kanji, and cover-art queries to keep dashboard reads fast as the SQLite database grows.
|
||||
- Immersion: Renamed the stats dashboard's Anime tab to Library so the media browser label matches non-anime sources like YouTube and other yt-dlp-backed content.
|
||||
- Anilist: Standardized episode completion threshold by introducing `DEFAULT_MIN_WATCH_RATIO` and using it for both local watched state transitions and AniList post-watch progress updates.
|
||||
- Anilist: Episode auto-marking now uses the same threshold as AniList (`85%`), removing divergent completion behavior.
|
||||
- Overlay: Excluded interjections and sound-effect tokens from subtitle annotation styling so they no longer inherit misleading lexical highlight treatment while still remaining visible and hoverable as plain subtitle tokens.
|
||||
- Overlay: Expanded subtitle annotation noise filtering to also strip annotation metadata from standalone grammar-only helper tokens such as particles, auxiliaries, adnominals, common explanatory endings like `んです` / `のだ`, and merged trailing quote-particle forms like `...って` while keeping them tokenized for hover lookup.
|
||||
|
||||
**Fixed**
|
||||
|
||||
- Launcher: Fixed mpv Lua plugin binary auto-detection on Linux to also search `/usr/bin/subminer` and `/usr/local/bin/subminer` (lowercase), matching the conventional Unix wrapper name used by packaged installs such as the AUR package.
|
||||
- Stats: Fixed the in-app stats overlay so it connects to the configured `stats.serverPort` instead of falling back to the default port.
|
||||
- Overlay: Fixed subtitle frequency tagging for merged lookup-backed tokens like `陰に` by falling back to exact surface-form Yomitan frequencies when the normalized headword lookup misses.
|
||||
- Overlay: Fixed MeCab merged-token position mapping across line breaks so merged content-plus-particle tokens like `陰に` keep their matched Yomitan frequency instead of inheriting shifted POS tags.
|
||||
- Overlay: Fixed grouped frequency parsing in both Yomitan and fallback frequency-dictionary lookups so display values like `118,121` use the leading rank instead of collapsing the rank and occurrence count into `118121`.
|
||||
- Overlay: Fixed frequency-rank ingestion to ignore Yomitan dictionaries explicitly marked `occurrence-based`, so raw occurrence counts are no longer treated as subtitle rank values.
|
||||
- Overlay: Fixed inflected headword frequency tagging to prefer ranks from the selected Yomitan `termsFind` popup entry itself, ordered by configured dictionary priority, so forms like `潜み` use primary-dictionary ranks like `4073` before falling back to lower-priority raw lemma metadata such as `CC100`.
|
||||
- Overlay: Fixed annotation-stage frequency filtering so exact kanji noun tokens like `者` keep their matched rank even when MeCab labels them `名詞/非自立`, instead of dropping the highlight after scan-time frequency lookup succeeds.
|
||||
- Anki: Fixed repeated character-dictionary startup work by scheduling auto-sync only from mpv media-path changes instead of also re-triggering it from connection and media-title events for the same title.
|
||||
- Overlay: Fixed macOS fullscreen overlay stability by keeping the passive visible overlay from stealing focus, re-raising the overlay window when reasserting its macOS topmost level, and tolerating one transient macOS tracker/helper miss before hiding the overlay.
|
||||
- Overlay: Kept subtitle tokenization warmup one-shot for the lifetime of the app so later fullscreen/media churn on macOS does not replay the startup warmup gate after the first file is ready.
|
||||
- Overlay: Added a bounded macOS tracker loss-grace window so fullscreen enter/leave transitions do not immediately hide and reload the overlay when the helper briefly loses the mpv window.
|
||||
- Overlay: Skipped subtitle/tokenization refresh invalidation on character-dictionary auto-sync completion when the dictionary was already current, preventing startup flash/reload loops on unchanged media.
|
||||
- Stats: Fixed session stats so known-word counts track real known-word occurrences without collapsing subtitle-line gaps.
|
||||
- Stats: Fixed session word totals in session-facing stats views to prefer token counts when available, preventing known words from exceeding total words in the session chart.
|
||||
- Stats: Fixed the stats Vocabulary tab blank-screen regression caused by a hook-order crash after vocabulary data finished loading.
|
||||
- Anki: Fixed card-mine OSD feedback so the final mine result stops the Anki spinner first, then shows a single-line `✓`/`x` status without being overwritten by a later spinner tick.
|
||||
- Stats: Removed the misleading `New words` series from expanded session charts; session detail now shows only the real total-word and known-word lines.
|
||||
- Stats: Restored the cross-anime word table behavior in stats vocabulary surfaces so shared vocabulary entries no longer disappear or merge incorrectly across related media.
|
||||
- Stats: `subminer stats -b` now runs as a standalone background stats daemon instead of reusing the main SubMiner app process, so the overlay app can still be launched separately for normal video watching.
|
||||
- Stats: Dashboard word mining still works against the background daemon by using a short-lived hidden helper for the Yomitan add-note flow.
|
||||
- Stats: Load full session timelines by default in stats session detail views so long sessions preserve complete telemetry history instead of being truncated by a fixed sample limit.
|
||||
- Stats: Replaced heuristic stats word counts with Yomitan token counts, so session, media, anime, and trend subtitle totals now come directly from parsed subtitle tokens.
|
||||
- Stats: Updated stats UI labels and lookup-rate copy to refer to tokens instead of words where those counts are shown.
|
||||
- Overlay: Reduced repeated `Overlay loading...` popups on macOS when fullscreen tracker flaps briefly hide and recover the visible overlay.
|
||||
- Stats: Scaled expanded session-detail known-word charts to the session's actual percentage range so small changes no longer render as a nearly flat line.
|
||||
- Jlpt: Reduced JLPT dictionary startup log noise by summarizing duplicate surface-form collisions instead of logging one line per duplicate entry.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.6.x</summary>
|
||||
|
||||
<h2>v0.6.5 (2026-03-15)</h2>
|
||||
|
||||
**Internal**
|
||||
|
||||
- Release: Seed the AUR checkout with the repo `.SRCINFO` template before rewriting metadata so tagged releases do not depend on prior AUR state.
|
||||
|
||||
<h2>v0.6.4 (2026-03-15)</h2>
|
||||
|
||||
**Internal**
|
||||
|
||||
- Release: Reworked AUR metadata generation to update `.SRCINFO` directly instead of depending on runner `makepkg`, fixing tagged release publishing for `subminer-bin`.
|
||||
|
||||
<h2>v0.6.3 (2026-03-15)</h2>
|
||||
|
||||
**Changed**
|
||||
|
||||
- Overlay: Expanded the `Alt+C` controller modal into an inline config/remap flow with preferred-controller saving and per-action learn mode for buttons, triggers, and stick directions.
|
||||
|
||||
**Internal**
|
||||
|
||||
- Workflow: Hardened the `subminer-scrum-master` skill to explicitly answer whether docs updates and changelog fragments are required before handoff.
|
||||
- Release: Automate `subminer-bin` AUR package updates from the tagged release workflow.
|
||||
|
||||
<h2>v0.6.2 (2026-03-12)</h2>
|
||||
|
||||
**Changed**
|
||||
|
||||
- Config: Added `yomitan.externalProfilePath` to reuse another Electron app's Yomitan profile in read-only mode.
|
||||
- Config: SubMiner now reuses external Yomitan dictionaries/settings without writing back to that profile.
|
||||
- Config: Launcher-managed playback now respects `yomitan.externalProfilePath` and no longer forces first-run setup when external Yomitan is configured.
|
||||
- Config: SubMiner now seeds `config.jsonc` even when the default config directory already exists.
|
||||
- Config: First-run setup now allows zero internal dictionaries when `yomitan.externalProfilePath` is configured, and falls back to requiring at least one internal dictionary if that external profile is later removed.
|
||||
|
||||
<h2>v0.6.1 (2026-03-12)</h2>
|
||||
|
||||
**Added**
|
||||
|
||||
- Overlay: Added Chrome Gamepad API controller support for keyboard-only overlay mode, including configurable logical bindings for lookup, mining, popup navigation, Yomitan audio, mpv pause, d-pad fallback navigation, and slower smooth popup scrolling.
|
||||
- Overlay: Added `Alt+C` controller selection and `Alt+Shift+C` controller debug modals, with preferred controller persistence and live raw input inspection.
|
||||
- Overlay: Added a transient in-overlay controller-detected indicator when a controller is first found.
|
||||
- Overlay: Fixed stale keyboard-only token highlight cleanup when keyboard-only mode turns off or the Yomitan popup closes.
|
||||
|
||||
**Docs**
|
||||
|
||||
- Install: Added Arch Linux AUR install docs for `subminer-bin` in the README and installation guide.
|
||||
|
||||
**Internal**
|
||||
|
||||
- Config: add an enforced `verify:config-example` gate so checked-in example config artifacts cannot drift silently
|
||||
- Release: Fixed the release workflow token permissions so tagged builds can download `oven-sh/setup-bun` and publish artifacts again.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.5.x</summary>
|
||||
|
||||
<h2>v0.5.6 (2026-03-10)</h2>
|
||||
|
||||
**Fixed**
|
||||
|
||||
- Dictionary: Persist merged character-dictionary MRU state as soon as a new retained set is built so revisits do not get dropped if later Yomitan import work fails, and skip merged dictionary rebuilds for reorder-only revisits when the retained anime set itself has not changed.
|
||||
- Startup: Fixed early Electron startup writing config and user data under a lowercase `~/.config/subminer` path instead of the canonical `~/.config/SubMiner` directory.
|
||||
- Overlay: Kept JLPT underline colors stable during Yomitan hover and selection states, even when tokens also use known, N+1, name-match, or frequency styling.
|
||||
|
||||
<h2>v0.5.5 (2026-03-09)</h2>
|
||||
|
||||
**Changed**
|
||||
|
||||
- Overlay: Added `f` as the default overlay fullscreen toggle and changed the default AniSkip intro-jump key to `Tab`.
|
||||
- Dictionary: Aligned AniList character dictionary generation more closely with the upstream reference by preserving duplicate shared names across characters, skipping characters without native Japanese names, restoring richer character info fields, and using upstream-style role mapping plus hint-aware kanji readings.
|
||||
- Startup: Ordered startup OSD messages so tokenization loads first, annotation loading appears next if still pending, and character dictionary sync progress waits until annotation loading finishes.
|
||||
- Dictionary: Added a visible startup OSD step for merged character-dictionary building so long rebuilds show progress before the later import/upload phase.
|
||||
|
||||
**Fixed**
|
||||
|
||||
- Dictionary: Fixed AniList media guessing for character dictionary auto-sync by using filename-only `guessit` input and preserving multi-part guessit titles instead of truncating them to the first segment.
|
||||
- Dictionary: Refresh the current subtitle after character dictionary auto-sync completes so newly imported character names highlight on the active line instead of waiting for the next subtitle change.
|
||||
- Dictionary: Show character dictionary auto-sync progress on the mpv OSD without sending desktop notifications.
|
||||
- Dictionary: Keep character dictionary auto-sync non-blocking during startup by letting snapshot/build work run in parallel and delaying only the Yomitan import/settings phase until current-media tokenization is already ready.
|
||||
- Overlay: Fixed visible overlay keyboard handling so pressing `Tab` still reaches mpv and triggers the default AniSkip skip-intro binding while the overlay has focus.
|
||||
- Plugin: Fix Windows mpv plugin binary override lookup so `SUBMINER_BINARY_PATH` still resolves to `SubMiner.exe` when no AppImage override is set.
|
||||
|
||||
<h2>v0.5.3 (2026-03-09)</h2>
|
||||
|
||||
**Changed**
|
||||
|
||||
- Release: Publish unsigned Windows `.exe` and `.zip` artifacts directly from release CI instead of routing them through SignPath.
|
||||
- Release: Added `bun run build:win:unsigned` for explicit local unsigned Windows packaging.
|
||||
|
||||
<h2>v0.5.2 (2026-03-09)</h2>
|
||||
|
||||
**Internal**
|
||||
|
||||
- Release: Pinned the Windows SignPath submission workflow to an explicit artifact-configuration slug instead of relying on the SignPath project's default configuration.
|
||||
|
||||
<h2>v0.5.1 (2026-03-09)</h2>
|
||||
|
||||
**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.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.3.x</summary>
|
||||
|
||||
<h2>v0.3.0 (2026-03-05)</h2>
|
||||
|
||||
- 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.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.2.x</summary>
|
||||
|
||||
<h2>v0.2.3 (2026-03-02)</h2>
|
||||
|
||||
- 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.
|
||||
|
||||
<h2>v0.2.2 (2026-03-01)</h2>
|
||||
|
||||
- Improved subtitle highlighting reliability for frequency modes.
|
||||
- Fixed Jellyfin misc info formatting cleanup.
|
||||
- Version bump maintenance for 0.2.2.
|
||||
|
||||
<h2>v0.2.1 (2026-03-01)</h2>
|
||||
|
||||
- Delivered Jellyfin and Subsync fixes from release patch cycle.
|
||||
- Version bump maintenance for 0.2.1.
|
||||
|
||||
<h2>v0.2.0 (2026-03-01)</h2>
|
||||
|
||||
- Added task-related release work for the overlay 2.0 cycle.
|
||||
- Introduced Overlay 2.0.
|
||||
- Improved release automation reliability.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>v0.1.x</summary>
|
||||
|
||||
<h2>v0.1.2 (2026-02-24)</h2>
|
||||
|
||||
- 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.
|
||||
|
||||
<h2>v0.1.1 (2026-02-23)</h2>
|
||||
|
||||
- Fixed overlay modal focus handling (`grab input`) behavior.
|
||||
- Version bump maintenance for 0.1.1.
|
||||
|
||||
<h2>v0.1.0 (2026-02-23)</h2>
|
||||
|
||||
- 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.
|
||||
|
||||
</details>
|
||||