mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-23 05:16:23 -07:00
Compare commits
134
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33e694bc61
|
||
|
|
430120cc63
|
||
|
|
294e3a777e
|
||
|
|
c673b71053
|
||
|
|
d31a28c54f
|
||
|
|
d100789232
|
||
|
|
9a5fb02cbd
|
||
|
|
7c161723cd | ||
|
|
2ba820f41a
|
||
|
|
c39dbb5a8d
|
||
|
|
2958591ec9
|
||
|
|
c9880efee3
|
||
|
|
f225ac3c89
|
||
|
|
cacaa2b371
|
||
|
|
b0fb39a8f5 | ||
|
|
a7a302bdd0 | ||
|
|
9fe4c7eb33
|
||
|
|
ab5e50adac
|
||
|
|
15c74ef502
|
||
|
|
f0d3c95b4c
|
||
|
|
d7e1d7edc6
|
||
|
|
6a1d1211ae
|
||
|
|
7585d430ae
|
||
|
|
122ff9a3c5
|
||
|
|
5354fdf7b9
|
||
|
|
d9fdc7ef6d
|
||
|
|
786bd5af81
|
||
|
|
42d1384c68 | ||
|
|
1508863dbb | ||
|
|
4dd30f44d1
|
||
|
|
ab48a5678e | ||
|
|
2f21582666 | ||
|
|
383aed8bad | ||
|
|
f9c4e892dc | ||
|
|
7959e1a4e5 | ||
|
|
917d52dd94 | ||
|
|
05425cc5db | ||
|
|
8097ed27e5 | ||
|
|
76e43d7e6d | ||
|
|
4f0762e840 | ||
|
|
026d495fac | ||
|
|
dd76782d30
|
||
|
|
a8c16147ad | ||
|
|
186d4a0640 | ||
|
|
2831e05124 | ||
|
|
6841a37a22 | ||
|
|
7c1eac03dc | ||
|
|
e6dc9dfec5
|
||
|
|
6d69a56574 | ||
|
|
0c37c665a2 | ||
|
|
8ae5bde6a4
|
||
|
|
016a43b804 | ||
|
|
fb2d1a512b | ||
|
|
614a8ca912 | ||
|
|
9eecb7358b
|
||
|
|
1e5d7747b4 | ||
|
|
c14c690875
|
||
|
|
84f718043a | ||
|
|
99266294b8 | ||
|
|
c055359be1
|
||
|
|
ec5a147095 | ||
|
|
a0635f4360 | ||
|
|
c0a78ef008 | ||
|
|
87b01155df | ||
|
|
fc5c49e365 | ||
|
|
a20269e9f5 | ||
|
|
2ad491e95c
|
||
|
|
6fffcc731f | ||
|
|
5cc21113fd | ||
|
|
051140f910 | ||
|
|
6e945f0872 | ||
|
|
c2c25c0da6 | ||
|
|
556de61756
|
||
|
|
a98fb0fddf
|
||
|
|
e816b3b371 | ||
|
|
ed7d3f4c3d | ||
|
|
509dc5bf7f | ||
|
|
0a0aa3ec98
|
||
|
|
b87cc3cfdd
|
||
|
|
8cb3c8c90a
|
||
|
|
03ea903927
|
||
|
|
3aea42e6f8
|
||
|
|
88bb3edfa4
|
||
|
|
9445aef004
|
||
|
|
c01bcd9d0f
|
||
|
|
695613b1e7
|
||
|
|
20797772f8 | ||
|
|
72c312810a
|
||
|
|
b61f050e78
|
||
|
|
2a77ba9dd4 | ||
|
|
4ed878270f
|
||
|
|
1d1b0c7bb6
|
||
|
|
db61ce358d
|
||
|
|
e9778a945a | ||
|
|
7de73e16a1 | ||
|
|
273652f781 | ||
|
|
14c0086520
|
||
|
|
ff0bc0af6d | ||
|
|
00b1b79bf4 | ||
|
|
e11a5fea0d
|
||
|
|
f73fe179d0
|
||
|
|
2938e7a32a | ||
|
|
82f6b4705a | ||
|
|
a02c33dac4 | ||
|
|
2174e689a2 | ||
|
|
7d729cb60c | ||
|
|
d963def1a9 | ||
|
|
6fb3d2eb13 | ||
|
|
c5a77ac067 | ||
|
|
8ddc151435
|
||
|
|
b98d4d65c7 | ||
|
|
046e74ea91
|
||
|
|
8bf847503d | ||
|
|
47b5903392
|
||
|
|
bf85554d1e | ||
|
|
d74c7e1235
|
||
|
|
57ddd19953 | ||
|
|
ee25536d90 | ||
|
|
7b0fbdf254 | ||
|
|
2fefc83e3f | ||
|
|
dbdf578c68 | ||
|
|
441ecf3c04 | ||
|
|
a0dde4ee3e
|
||
|
|
fe4dacc1e7 | ||
|
|
b08cd0db35 | ||
|
|
bffb1c5982 | ||
|
|
5b8848518a | ||
|
|
176edd67f1
|
||
|
|
4d65dec340 | ||
|
|
6607c333bc
|
||
|
|
b2bbf1ae12
|
||
|
|
b204d4dd6e | ||
|
|
89ed675935 | ||
|
|
1995200e76
|
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"name": "subminer-local",
|
||||
"interface": {
|
||||
"displayName": "SubMiner Local"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "subminer-workflow",
|
||||
"source": {
|
||||
"source": "local",
|
||||
"path": "./plugins/subminer-workflow"
|
||||
},
|
||||
"policy": {
|
||||
"installation": "AVAILABLE",
|
||||
"authentication": "ON_INSTALL"
|
||||
},
|
||||
"category": "Productivity"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,22 +1,45 @@
|
||||
---
|
||||
name: 'subminer-change-verification'
|
||||
description: 'Compatibility shim. Canonical SubMiner change verification workflow now lives in the repo-local subminer-workflow plugin.'
|
||||
name: subminer-change-verification
|
||||
description: Verify SubMiner changes with repo-native cheap-first test lanes. Use after code, config, launcher, plugin, runtime, stats, documentation, or workflow changes; do not use for read-only questions.
|
||||
---
|
||||
|
||||
# Compatibility Shim
|
||||
# SubMiner Change Verification
|
||||
|
||||
Canonical source:
|
||||
Verify the behavior claimed by a change without running unrelated expensive checks by default.
|
||||
|
||||
- `plugins/subminer-workflow/skills/subminer-change-verification/SKILL.md`
|
||||
## Workflow
|
||||
|
||||
Canonical helper scripts:
|
||||
1. Inspect the requested scope and changed paths with `git status --short` and `git diff`.
|
||||
2. Read `docs/workflow/verification.md` as the source of truth for maintained lanes.
|
||||
3. Run the cheapest lane or lanes that cover the changed behavior.
|
||||
4. Escalate to the full handoff gate only for substantial or cross-boundary changes.
|
||||
5. Report exact commands, results, skipped checks, blockers, and remaining risk.
|
||||
|
||||
- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh`
|
||||
- `plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh`
|
||||
Do not use hidden wrapper commands. Verification commands are owned by `package.json` and the workflow documentation.
|
||||
|
||||
When this shim is invoked:
|
||||
## Lane Selection
|
||||
|
||||
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.
|
||||
- Internal docs, `AGENTS.md`, or `.agents/skills/**`: `bun run test:docs:kb`
|
||||
- User-facing `docs-site/**`: `bun run docs:test`, then `bun run docs:build`
|
||||
- Config/schema/defaults: `bun run test:config`
|
||||
- If defaults or templates changed, also run `bun run generate:config-example` and `bun run verify:config-example`.
|
||||
- General TypeScript source: `bun run typecheck`, then `bun run test:fast`
|
||||
- Launcher or mpv plugin: `bun run test:launcher` or `bun run test:env`, based on the behavior changed
|
||||
- Runtime compatibility or dist-sensitive wiring: `bun run test:runtime:compat`
|
||||
- Stats dashboard: `bun run test:stats`
|
||||
- Build/release scripts: `bun run test:scripts`
|
||||
|
||||
For substantial changes, use the full gate documented in `AGENTS.md` and `docs/workflow/verification.md`.
|
||||
|
||||
## Runtime Escalation
|
||||
|
||||
Real runtime checks are required when the claim depends on actual Electron, mpv, overlay, focus, window tracking, launch, or socket behavior. Run the relevant application flow when the environment supports it. Otherwise, report the missing runtime dependency and do not present cheaper checks as authoritative runtime validation.
|
||||
|
||||
## Pre-Handoff Checks
|
||||
|
||||
Before handoff, reconcile both questions:
|
||||
|
||||
1. Do behavior, defaults, flags, shortcuts, ports, APIs, architecture, or workflow changes require documentation updates?
|
||||
2. Does the change require a current-outcome fragment under `changes/` according to `changes/README.md`?
|
||||
|
||||
Complete required updates before handoff or report the blocker.
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
REPO_ROOT=$(cd "$SCRIPT_DIR/../../../.." && pwd)
|
||||
TARGET="$REPO_ROOT/plugins/subminer-workflow/skills/subminer-change-verification/scripts/classify_subminer_diff.sh"
|
||||
|
||||
if [[ ! -x "$TARGET" ]]; then
|
||||
echo "Missing canonical script: $TARGET" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec "$TARGET" "$@"
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
||||
REPO_ROOT=$(cd "$SCRIPT_DIR/../../../.." && pwd)
|
||||
TARGET="$REPO_ROOT/plugins/subminer-workflow/skills/subminer-change-verification/scripts/verify_subminer_change.sh"
|
||||
|
||||
if [[ ! -x "$TARGET" ]]; then
|
||||
echo "Missing canonical script: $TARGET" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
exec "$TARGET" "$@"
|
||||
@@ -0,0 +1,34 @@
|
||||
---
|
||||
name: subminer-release
|
||||
description: Prepare, cut, publish, or repair SubMiner stable and prerelease releases. Use for hands-on release work; do not use for general release questions.
|
||||
---
|
||||
|
||||
# SubMiner release
|
||||
|
||||
Carry out the requested release phase using the repository's current release process.
|
||||
|
||||
## Source of truth
|
||||
|
||||
Read `docs/RELEASING.md` completely before changing files or release state. Treat it as canonical. Read `changes/README.md` when the work touches change fragments or generated release notes.
|
||||
|
||||
Do not copy release commands or policy into this skill. If this skill disagrees with the release guide, follow the guide and reconcile the skill before handoff.
|
||||
|
||||
## Workflow
|
||||
|
||||
1. Identify whether the request is for a stable release, prerelease, release preparation, publication, or repair.
|
||||
2. Inspect the current branch, worktree status, package version, pending change fragments, relevant tags, and latest CI state before making changes.
|
||||
3. Follow the matching procedure in `docs/RELEASING.md` in order. Review generated changelog and release-note Markdown before it can be committed or published.
|
||||
4. Run every required gate for the requested release phase. Do not treat a cheaper test lane as a substitute for the documented release gate.
|
||||
5. Before a stable tag, confirm the package and tag versions match and no pending `changes/*.md` fragments remain. Preserve fragments for prereleases as documented.
|
||||
6. Report the resulting version, completed checks, local commit and tag state, remote publication state, skipped platform checks, and any remaining manual work.
|
||||
|
||||
## Authorization boundaries
|
||||
|
||||
- A request to prepare a release stops before commit, tag, push, or remote publication unless the user also authorizes those actions.
|
||||
- A clear request to cut or publish a release includes the documented commit, tag, and push steps. Ask before the first remote mutation when the wording is ambiguous.
|
||||
- Do not edit an existing GitHub release, publish to the AUR, change secrets, or alter signing configuration unless the user explicitly requests that operation.
|
||||
- Do not switch branches without consent.
|
||||
|
||||
## Stop conditions
|
||||
|
||||
Stop and report the blocker when required CI or a release gate fails, authentication is missing, versions disagree, required artifacts are absent, or the worktree contains unexpected changes that overlap the release. Do not tag or publish a partially verified release.
|
||||
@@ -1,18 +0,0 @@
|
||||
---
|
||||
name: 'subminer-scrum-master'
|
||||
description: 'Compatibility shim. Canonical SubMiner scrum-master workflow now lives in the repo-local subminer-workflow plugin.'
|
||||
---
|
||||
|
||||
# Compatibility Shim
|
||||
|
||||
Canonical source:
|
||||
|
||||
- `plugins/subminer-workflow/skills/subminer-scrum-master/SKILL.md`
|
||||
|
||||
When this shim is invoked:
|
||||
|
||||
1. Read the canonical plugin-owned skill.
|
||||
2. Follow the plugin-owned skill as the source of truth.
|
||||
3. Do not duplicate workflow changes here; update the plugin-owned skill instead.
|
||||
|
||||
This shim exists so existing repo references and prompts keep resolving during the migration to the repo-local plugin workflow.
|
||||
@@ -32,9 +32,11 @@ jobs:
|
||||
- name: Guard stable docs tag shape
|
||||
id: tag_guard
|
||||
if: github.ref_type == 'tag'
|
||||
env:
|
||||
TAG_NAME: ${{ github.ref_name }}
|
||||
run: |
|
||||
if [[ ! "${{ github.ref_name }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::notice::Skipping non-stable docs tag ${{ github.ref_name }}"
|
||||
if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
echo "::notice::Skipping non-stable docs tag $TAG_NAME"
|
||||
echo "stable_tag=false" >> "$GITHUB_OUTPUT"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
name: Package release
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
CSC_LINK:
|
||||
required: true
|
||||
CSC_KEY_PASSWORD:
|
||||
required: true
|
||||
APPLE_ID:
|
||||
required: true
|
||||
APPLE_APP_SPECIFIC_PASSWORD:
|
||||
required: true
|
||||
APPLE_TEAM_ID:
|
||||
required: true
|
||||
# Project TMDB key baked into release artifacts; builds stay valid without it.
|
||||
SUBMINER_TMDB_API_KEY:
|
||||
required: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
||||
- 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/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: Build texthooker-ui
|
||||
run: |
|
||||
cd vendor/texthooker-ui
|
||||
bun install --frozen-lockfile
|
||||
bun run build
|
||||
|
||||
- name: Download previous package size reports
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mkdir -p .tmp/package-baseline
|
||||
previous=$(gh api "repos/$GITHUB_REPOSITORY/releases" --jq '[.[] | select(.draft == false and .tag_name != env.GITHUB_REF_NAME)] | sort_by(.published_at) | last | .tag_name // empty')
|
||||
if [ -n "$previous" ]; then
|
||||
gh release download "$previous" --pattern 'package-size-*.json' --dir .tmp/package-baseline || echo 'Previous release has no package size reports; size comparison will be skipped.'
|
||||
fi
|
||||
|
||||
- name: Build AppImage
|
||||
run: bun run build:appimage
|
||||
env:
|
||||
SUBMINER_TMDB_API_KEY: ${{ secrets.SUBMINER_TMDB_API_KEY }}
|
||||
|
||||
- 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: Smoke packaged runtime assets
|
||||
shell: bash
|
||||
run: xvfb-run -a bun run test:package "release/linux-unpacked/resources"
|
||||
|
||||
- name: Upload AppImage artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: appimage
|
||||
path: |
|
||||
release/*.AppImage
|
||||
release/latest*.yml
|
||||
release/*.blockmap
|
||||
release/package-size-*.json
|
||||
if-no-files-found: error
|
||||
|
||||
build-macos:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
||||
- 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/bun.lock', '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 --frozen-lockfile
|
||||
bun run build
|
||||
|
||||
- name: Download previous package size reports
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mkdir -p .tmp/package-baseline
|
||||
previous=$(gh api "repos/$GITHUB_REPOSITORY/releases" --jq '[.[] | select(.draft == false and .tag_name != env.GITHUB_REF_NAME)] | sort_by(.published_at) | last | .tag_name // empty')
|
||||
if [ -n "$previous" ]; then
|
||||
gh release download "$previous" --pattern 'package-size-*.json' --dir .tmp/package-baseline || echo 'Previous release has no package size reports; size comparison will be skipped.'
|
||||
fi
|
||||
|
||||
- name: Build signed + notarized macOS artifacts
|
||||
run: bun run build:mac
|
||||
env:
|
||||
SUBMINER_TMDB_API_KEY: ${{ secrets.SUBMINER_TMDB_API_KEY }}
|
||||
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: Smoke packaged runtime assets
|
||||
shell: bash
|
||||
run: bun run test:package "release/mac-arm64/SubMiner.app/Contents/Resources"
|
||||
|
||||
- name: Upload macOS artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos
|
||||
path: |
|
||||
release/*.dmg
|
||||
release/*.zip
|
||||
release/latest*.yml
|
||||
release/*.blockmap
|
||||
release/package-size-*.json
|
||||
if-no-files-found: error
|
||||
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
persist-credentials: false
|
||||
|
||||
- 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/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: Build texthooker-ui
|
||||
shell: powershell
|
||||
run: |
|
||||
Set-Location vendor/texthooker-ui
|
||||
bun install --frozen-lockfile
|
||||
bun run build
|
||||
|
||||
- name: Download previous package size reports
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
mkdir -p .tmp/package-baseline
|
||||
previous=$(gh api "repos/$GITHUB_REPOSITORY/releases" --jq '[.[] | select(.draft == false and .tag_name != env.GITHUB_REF_NAME)] | sort_by(.published_at) | last | .tag_name // empty')
|
||||
if [ -n "$previous" ]; then
|
||||
gh release download "$previous" --pattern 'package-size-*.json' --dir .tmp/package-baseline || echo 'Previous release has no package size reports; size comparison will be skipped.'
|
||||
fi
|
||||
|
||||
- name: Verify managed Windows launcher
|
||||
run: bun test src/main/runtime/managed-launcher.test.ts
|
||||
|
||||
- name: Verify Windows launcher bootstrap
|
||||
run: bun test src/main/runtime/windows-launcher-bootstrap.test.ts
|
||||
|
||||
- name: Build unsigned Windows artifacts
|
||||
run: bun run build:win:unsigned
|
||||
env:
|
||||
SUBMINER_TMDB_API_KEY: ${{ secrets.SUBMINER_TMDB_API_KEY }}
|
||||
|
||||
- name: Smoke packaged runtime assets
|
||||
shell: bash
|
||||
run: bun run test:package "release/win-unpacked/resources"
|
||||
|
||||
- name: Upload Windows artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: windows
|
||||
path: |
|
||||
release/*.exe
|
||||
release/*.zip
|
||||
release/latest*.yml
|
||||
release/*.blockmap
|
||||
release/package-size-*.json
|
||||
if-no-files-found: error
|
||||
@@ -16,201 +16,21 @@ jobs:
|
||||
contents: read
|
||||
uses: ./.github/workflows/quality-gate.yml
|
||||
|
||||
build-linux:
|
||||
package:
|
||||
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/latest*.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/latest*.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/latest*.yml
|
||||
release/*.blockmap
|
||||
if-no-files-found: error
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/package-release.yml
|
||||
secrets:
|
||||
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 }}
|
||||
SUBMINER_TMDB_API_KEY: ${{ secrets.SUBMINER_TMDB_API_KEY }}
|
||||
|
||||
release:
|
||||
needs: [build-linux, build-macos, build-windows]
|
||||
needs: [package]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -256,11 +76,11 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Build Bun subminer wrapper
|
||||
- name: Build launcher runtime artifacts
|
||||
run: make build-launcher
|
||||
|
||||
- name: Verify Bun subminer wrapper
|
||||
run: dist/launcher/subminer --help >/dev/null
|
||||
- name: Smoke launcher bundle
|
||||
run: bun dist/launcher/subminer.js --help >/dev/null
|
||||
|
||||
- name: Enforce generated launcher workflow
|
||||
run: bash scripts/verify-generated-launcher.sh
|
||||
@@ -274,12 +94,18 @@ jobs:
|
||||
config.example.jsonc \
|
||||
plugin/subminer \
|
||||
plugin/subminer.conf \
|
||||
assets/themes/subminer.rasi
|
||||
assets/themes/subminer.rasi \
|
||||
assets/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer \
|
||||
resources/bun/licenses
|
||||
|
||||
- name: Package Bun corresponding source
|
||||
run: bun scripts/package-bun-source.mjs
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
files=(release/*.AppImage release/*.dmg release/*.exe release/*.zip release/*.tar.gz release/latest*.yml release/*.blockmap dist/launcher/subminer)
|
||||
files=(release/*.AppImage release/*.dmg release/*.exe release/*.zip release/*.tar.gz release/latest*.yml release/*.blockmap dist/launcher/subminer dist/launcher/subminer.cmd)
|
||||
files+=(release/package-size-*.json)
|
||||
if [ "${#files[@]}" -eq 0 ]; then
|
||||
echo "No release artifacts found for checksum generation."
|
||||
exit 1
|
||||
@@ -296,15 +122,22 @@ jobs:
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Verify committed prerelease notes
|
||||
env:
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
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
|
||||
if ! bun run changelog:check-prerelease-notes --version "$RELEASE_VERSION"; then
|
||||
echo "::error::release/prerelease-notes.md was not generated for $RELEASE_VERSION. Rerun 'bun run changelog:prerelease-notes --version $RELEASE_VERSION' locally, commit, and retag."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Publish Prerelease
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
@@ -315,10 +148,13 @@ jobs:
|
||||
release/*.exe
|
||||
release/*.zip
|
||||
release/*.tar.gz
|
||||
release/*.tar.gz.sha256
|
||||
release/latest*.yml
|
||||
release/*.blockmap
|
||||
release/SHA256SUMS.txt
|
||||
release/package-size-*.json
|
||||
dist/launcher/subminer
|
||||
dist/launcher/subminer.cmd
|
||||
)
|
||||
|
||||
if [ "${#artifacts[@]}" -eq 0 ]; then
|
||||
@@ -326,27 +162,27 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if gh release view "${{ steps.version.outputs.VERSION }}" >/dev/null 2>&1; then
|
||||
gh release edit "${{ steps.version.outputs.VERSION }}" \
|
||||
if gh release view "$RELEASE_VERSION" >/dev/null 2>&1; then
|
||||
gh release edit "$RELEASE_VERSION" \
|
||||
--draft \
|
||||
--prerelease \
|
||||
--title "${{ steps.version.outputs.VERSION }}" \
|
||||
--title "$RELEASE_VERSION" \
|
||||
--notes-file release/prerelease-notes.md
|
||||
else
|
||||
gh release create "${{ steps.version.outputs.VERSION }}" \
|
||||
gh release create "$RELEASE_VERSION" \
|
||||
--draft \
|
||||
--latest=false \
|
||||
--prerelease \
|
||||
--title "${{ steps.version.outputs.VERSION }}" \
|
||||
--title "$RELEASE_VERSION" \
|
||||
--notes-file release/prerelease-notes.md
|
||||
fi
|
||||
|
||||
for asset in "${artifacts[@]}"; do
|
||||
gh release upload "${{ steps.version.outputs.VERSION }}" "$asset" --clobber
|
||||
gh release upload "$RELEASE_VERSION" "$asset" --clobber
|
||||
done
|
||||
|
||||
gh release edit "${{ steps.version.outputs.VERSION }}" \
|
||||
gh release edit "$RELEASE_VERSION" \
|
||||
--draft=false \
|
||||
--prerelease \
|
||||
--title "${{ steps.version.outputs.VERSION }}" \
|
||||
--title "$RELEASE_VERSION" \
|
||||
--notes-file release/prerelease-notes.md
|
||||
|
||||
@@ -7,6 +7,34 @@ permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
launcher-runtime:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [windows-latest, macos-latest]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Verify native runtime staging
|
||||
run: bun test src/main/runtime/managed-launcher.test.ts
|
||||
|
||||
- name: Verify Windows launcher bootstrap
|
||||
run: bun test src/main/runtime/windows-launcher-bootstrap.test.ts
|
||||
|
||||
- name: Verify native mpv process launch
|
||||
run: bun test src/main/runtime/mpv-process.test.ts
|
||||
|
||||
- name: Verify POSIX launcher bootstrap
|
||||
run: bun test src/main/runtime/posix-launcher-bootstrap.test.ts
|
||||
|
||||
quality-gate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@@ -60,17 +88,28 @@ jobs:
|
||||
|
||||
- name: Install Lua
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y lua5.4
|
||||
# Lua needs only Ubuntu sources; unrelated runner repositories can be unavailable.
|
||||
test -f /etc/apt/sources.list.d/ubuntu.sources
|
||||
apt_sources=(-o Dir::Etc::sourcelist=sources.list.d/ubuntu.sources -o Dir::Etc::sourceparts=-)
|
||||
sudo apt-get "${apt_sources[@]}" update
|
||||
sudo apt-get "${apt_sources[@]}" 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: Launcher unit and script suites
|
||||
run: bun run test:launcher:unit:src && bun run test:scripts
|
||||
|
||||
- name: Environment suite
|
||||
run: bun run test:env
|
||||
|
||||
- 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: Coverage suite (maintained source lane)
|
||||
run: bun run test:coverage:src
|
||||
|
||||
@@ -84,17 +123,6 @@ jobs:
|
||||
- name: Stats UI tests
|
||||
run: bun run test:stats
|
||||
|
||||
- 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
|
||||
|
||||
@@ -107,11 +135,11 @@ jobs:
|
||||
- name: Security audit
|
||||
run: bun audit --audit-level high
|
||||
|
||||
- name: Build Bun subminer wrapper
|
||||
- name: Build launcher runtime artifacts
|
||||
run: make build-launcher
|
||||
|
||||
- name: Verify Bun subminer wrapper
|
||||
run: dist/launcher/subminer --help >/dev/null
|
||||
- name: Smoke launcher bundle
|
||||
run: bun dist/launcher/subminer.js --help >/dev/null
|
||||
|
||||
- name: Enforce generated launcher workflow
|
||||
run: bash scripts/verify-generated-launcher.sh
|
||||
|
||||
+50
-208
@@ -17,199 +17,21 @@ jobs:
|
||||
contents: read
|
||||
uses: ./.github/workflows/quality-gate.yml
|
||||
|
||||
build-linux:
|
||||
package:
|
||||
needs: [quality-gate]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
stats/node_modules
|
||||
vendor/texthooker-ui/node_modules
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Build texthooker-ui
|
||||
run: |
|
||||
cd vendor/texthooker-ui
|
||||
bun install
|
||||
bun run build
|
||||
|
||||
- name: Build AppImage
|
||||
run: bun run build:appimage
|
||||
|
||||
- name: Build unversioned AppImage
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
appimages=(release/SubMiner-*.AppImage)
|
||||
if [ "${#appimages[@]}" -eq 0 ]; then
|
||||
echo "No versioned AppImage found to create unversioned artifact."
|
||||
ls -la release
|
||||
exit 1
|
||||
fi
|
||||
cp "${appimages[0]}" release/SubMiner.AppImage
|
||||
|
||||
- name: Upload AppImage artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: appimage
|
||||
path: |
|
||||
release/*.AppImage
|
||||
release/latest*.yml
|
||||
release/*.blockmap
|
||||
|
||||
build-macos:
|
||||
needs: [quality-gate]
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- name: Setup Bun
|
||||
uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: 1.3.5
|
||||
|
||||
- name: Cache dependencies
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
node_modules
|
||||
stats/node_modules
|
||||
vendor/texthooker-ui/node_modules
|
||||
vendor/subminer-yomitan/node_modules
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('bun.lock', 'stats/bun.lock', 'vendor/texthooker-ui/package.json', 'vendor/subminer-yomitan/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-bun-
|
||||
|
||||
- name: Validate macOS signing/notarization secrets
|
||||
run: |
|
||||
missing=0
|
||||
for name in CSC_LINK CSC_KEY_PASSWORD APPLE_ID APPLE_APP_SPECIFIC_PASSWORD APPLE_TEAM_ID; do
|
||||
if [ -z "${!name}" ]; then
|
||||
echo "Missing required secret: $name"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
if [ "$missing" -ne 0 ]; then
|
||||
echo "Set all required macOS signing/notarization secrets and rerun."
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
bun install --frozen-lockfile
|
||||
cd stats && bun install --frozen-lockfile
|
||||
|
||||
- name: Build texthooker-ui
|
||||
run: |
|
||||
cd vendor/texthooker-ui
|
||||
bun install
|
||||
bun run build
|
||||
|
||||
- name: Build signed + notarized macOS artifacts
|
||||
run: bun run build:mac
|
||||
env:
|
||||
CSC_LINK: ${{ secrets.CSC_LINK }}
|
||||
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
|
||||
- name: Upload macOS artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos
|
||||
path: |
|
||||
release/*.dmg
|
||||
release/*.zip
|
||||
release/latest*.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/latest*.yml
|
||||
release/*.blockmap
|
||||
if-no-files-found: error
|
||||
permissions:
|
||||
contents: read
|
||||
uses: ./.github/workflows/package-release.yml
|
||||
secrets:
|
||||
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 }}
|
||||
SUBMINER_TMDB_API_KEY: ${{ secrets.SUBMINER_TMDB_API_KEY }}
|
||||
|
||||
release:
|
||||
needs: [build-linux, build-macos, build-windows]
|
||||
needs: [package]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -255,11 +77,11 @@ jobs:
|
||||
- name: Install dependencies
|
||||
run: bun install --frozen-lockfile
|
||||
|
||||
- name: Build Bun subminer wrapper
|
||||
- name: Build launcher runtime artifacts
|
||||
run: make build-launcher
|
||||
|
||||
- name: Verify Bun subminer wrapper
|
||||
run: dist/launcher/subminer --help >/dev/null
|
||||
- name: Smoke launcher bundle
|
||||
run: bun dist/launcher/subminer.js --help >/dev/null
|
||||
|
||||
- name: Enforce generated launcher workflow
|
||||
run: bash scripts/verify-generated-launcher.sh
|
||||
@@ -273,12 +95,18 @@ jobs:
|
||||
config.example.jsonc \
|
||||
plugin/subminer \
|
||||
plugin/subminer.conf \
|
||||
assets/themes/subminer.rasi
|
||||
assets/themes/subminer.rasi \
|
||||
assets/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer \
|
||||
resources/bun/licenses
|
||||
|
||||
- name: Package Bun corresponding source
|
||||
run: bun scripts/package-bun-source.mjs
|
||||
|
||||
- name: Generate checksums
|
||||
run: |
|
||||
shopt -s nullglob
|
||||
files=(release/*.AppImage release/*.dmg release/*.exe release/*.zip release/*.tar.gz release/latest*.yml release/*.blockmap dist/launcher/subminer)
|
||||
files=(release/*.AppImage release/*.dmg release/*.exe release/*.zip release/*.tar.gz release/latest*.yml release/*.blockmap dist/launcher/subminer dist/launcher/subminer.cmd)
|
||||
files+=(release/package-size-*.json)
|
||||
if [ "${#files[@]}" -eq 0 ]; then
|
||||
echo "No release artifacts found for checksum generation."
|
||||
exit 1
|
||||
@@ -295,33 +123,40 @@ jobs:
|
||||
run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Guard against pending changelog fragments
|
||||
env:
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: |
|
||||
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."
|
||||
echo "::error::Pending changelog fragments detected. Run 'bun run changelog:build --version $RELEASE_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
|
||||
|
||||
- name: Verify changelog is ready for tagged release
|
||||
run: bun run changelog:check --version "${{ steps.version.outputs.VERSION }}"
|
||||
env:
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: bun run changelog:check --version "$RELEASE_VERSION"
|
||||
|
||||
- name: Generate release notes from changelog
|
||||
run: bun run changelog:release-notes --version "${{ steps.version.outputs.VERSION }}"
|
||||
env:
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: bun run changelog:release-notes --version "$RELEASE_VERSION"
|
||||
|
||||
- name: Publish Release
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
|
||||
if gh release view "${{ steps.version.outputs.VERSION }}" >/dev/null 2>&1; then
|
||||
if gh release view "$RELEASE_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 }}" \
|
||||
gh release edit "$RELEASE_VERSION" \
|
||||
--draft=false \
|
||||
--title "${{ steps.version.outputs.VERSION }}" \
|
||||
--title "$RELEASE_VERSION" \
|
||||
--notes-file release/release-notes.md
|
||||
else
|
||||
gh release create "${{ steps.version.outputs.VERSION }}" \
|
||||
--title "${{ steps.version.outputs.VERSION }}" \
|
||||
gh release create "$RELEASE_VERSION" \
|
||||
--title "$RELEASE_VERSION" \
|
||||
--notes-file release/release-notes.md
|
||||
fi
|
||||
|
||||
@@ -332,10 +167,13 @@ jobs:
|
||||
release/*.exe
|
||||
release/*.zip
|
||||
release/*.tar.gz
|
||||
release/*.tar.gz.sha256
|
||||
release/latest*.yml
|
||||
release/*.blockmap
|
||||
release/SHA256SUMS.txt
|
||||
release/package-size-*.json
|
||||
dist/launcher/subminer
|
||||
dist/launcher/subminer.cmd
|
||||
)
|
||||
|
||||
if [ "${#artifacts[@]}" -eq 0 ]; then
|
||||
@@ -344,7 +182,7 @@ jobs:
|
||||
fi
|
||||
|
||||
for asset in "${artifacts[@]}"; do
|
||||
gh release upload "${{ steps.version.outputs.VERSION }}" "$asset" --clobber
|
||||
gh release upload "$RELEASE_VERSION" "$asset" --clobber
|
||||
done
|
||||
|
||||
aur-publish:
|
||||
@@ -420,9 +258,10 @@ jobs:
|
||||
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 }}
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="${{ steps.version.outputs.VERSION }}"
|
||||
version="$RELEASE_VERSION"
|
||||
install -dm755 .tmp/aur-release-assets
|
||||
gh release download "$version" \
|
||||
--dir .tmp/aur-release-assets \
|
||||
@@ -432,15 +271,17 @@ jobs:
|
||||
|
||||
- name: Update AUR packaging metadata
|
||||
if: steps.aur_prereqs.outputs.skip != 'true' && steps.aur_ssh.outputs.skip != 'true' && steps.aur_clone.outputs.skip != 'true'
|
||||
env:
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version_no_v="${{ steps.version.outputs.VERSION }}"
|
||||
version_no_v="$RELEASE_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 }}" \
|
||||
--version "$RELEASE_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"
|
||||
@@ -450,6 +291,7 @@ jobs:
|
||||
working-directory: aur-subminer-bin
|
||||
env:
|
||||
GIT_SSH_COMMAND: ssh -i ~/.ssh/aur -o IdentitiesOnly=yes
|
||||
RELEASE_VERSION: ${{ steps.version.outputs.VERSION }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if git diff --quiet -- PKGBUILD .SRCINFO; then
|
||||
@@ -459,7 +301,7 @@ jobs:
|
||||
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 }}"
|
||||
git commit -m "Update to $RELEASE_VERSION"
|
||||
|
||||
attempts=3
|
||||
for attempt in $(seq 1 "$attempts"); do
|
||||
|
||||
@@ -20,6 +20,8 @@ coverage/
|
||||
/main-entry.js
|
||||
/main-entry.js.map
|
||||
|
||||
/build/hachidori/
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
@@ -49,6 +51,7 @@ tests/*
|
||||
!.agents/skills/
|
||||
.agents/skills/*
|
||||
!.agents/skills/subminer-change-verification/
|
||||
!.agents/skills/subminer-release/
|
||||
!.agents/skills/subminer-scrum-master/
|
||||
.agents/skills/subminer-change-verification/*
|
||||
!.agents/skills/subminer-change-verification/SKILL.md
|
||||
@@ -56,6 +59,8 @@ tests/*
|
||||
.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-release/*
|
||||
!.agents/skills/subminer-release/SKILL.md
|
||||
.agents/skills/subminer-scrum-master/*
|
||||
!.agents/skills/subminer-scrum-master/SKILL.md
|
||||
favicon.png
|
||||
|
||||
@@ -8,3 +8,7 @@
|
||||
[submodule "vendor/subminer-yomitan"]
|
||||
path = vendor/subminer-yomitan
|
||||
url = https://github.com/ksyasuda/subminer-yomitan
|
||||
[submodule "vendor/hachidori"]
|
||||
path = vendor/hachidori
|
||||
url = https://github.com/ksyasuda/hachidori.git
|
||||
branch = subminer
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# AGENTS.MD
|
||||
# AGENTS.md
|
||||
|
||||
## Internal Docs
|
||||
|
||||
@@ -13,7 +13,7 @@ Start here, then leave this file.
|
||||
|
||||
`docs-site/` is user-facing. Do not treat it as the canonical internal source of truth.
|
||||
|
||||
`CLAUDE.md` is a symlink to this file — there is one project instruction file, not two.
|
||||
`CLAUDE.md` is a symlink to this file; there is one project instruction file, not two.
|
||||
|
||||
## Quick Start
|
||||
|
||||
@@ -25,8 +25,9 @@ Start here, then leave this file.
|
||||
|
||||
## Build / Test
|
||||
|
||||
- Runtime/package manager: Bun (`packageManager: bun@1.3.5`)
|
||||
- Default handoff gate:
|
||||
- Runtime/package manager: Bun; use the version pinned by `package.json`.
|
||||
- Follow [`docs/workflow/verification.md`](./docs/workflow/verification.md) and start with the cheapest sufficient lane.
|
||||
- Full handoff gate for substantial changes:
|
||||
`bun run typecheck`
|
||||
`bun run test:fast`
|
||||
`bun run test:env`
|
||||
@@ -44,13 +45,15 @@ Start here, then leave this file.
|
||||
- Runtime-compat / dist-sensitive: `bun run test:runtime:compat`
|
||||
- Stats dashboard UI (`stats/`): `bun run test:stats`
|
||||
- Build/release scripts (`scripts/**`): `bun run test:scripts`
|
||||
- Docs-only: `bun run docs:test`, then `bun run docs:build`
|
||||
- Internal docs, `AGENTS.md`, or repo skills: `bun run test:docs:kb`
|
||||
- User-facing `docs-site/`: `bun run docs:test`, then `bun run docs:build`
|
||||
- macOS mpv window helper: `bun test scripts/get-mpv-window-macos.test.ts`
|
||||
- Test lanes are directory-discovered via `scripts/test-lanes.ts`; never hand-list test files in `package.json`
|
||||
|
||||
## Docs Upkeep
|
||||
|
||||
- Docs ship with the change, not after. If a change alters behavior, defaults, flags, shortcuts, ports, or APIs, update the matching docs in the same PR. Touching code without reconciling its docs is an incomplete change.
|
||||
- Source of truth for config defaults is the generated `config.example.jsonc`. Never write a default value into prose you didn't read from it — and don't restate the same default across multiple docs; cite/link to one place so there's a single thing to update.
|
||||
- Source of truth for config defaults is the generated `config.example.jsonc`. Never write a default value into prose you didn't read from it, and don't restate the same default across multiple docs; cite/link to one place so there's a single thing to update.
|
||||
- Trigger map (touch left → update right):
|
||||
- `src/config/definitions/**` (schema/defaults/template) → `bun run generate:config-example`, then reconcile `docs-site/configuration.md` + any feature doc that cites that default
|
||||
- shortcuts/keybindings (`shortcuts.*`, `keybindings`, `stats.*Key`, `subtitleSidebar.toggleKey`, controller bindings) → `docs-site/shortcuts.md`
|
||||
@@ -71,16 +74,10 @@ Start here, then leave this file.
|
||||
|
||||
## Release / PR Notes
|
||||
|
||||
- User-visible PRs need reconciled current-outcome fragment(s) in `changes/*.md` — format and rules in [`changes/README.md`](./changes/README.md) (`type` + `area` keys required; inspect existing same-PR fragments, then update/remove stale bullets or add only genuinely separate outcomes; apply the `skip-changelog` label to opt out)
|
||||
- User-visible PRs need reconciled current-outcome fragment(s) in `changes/*.md`. Format and rules live in [`changes/README.md`](./changes/README.md) (`type` + `area` keys required; inspect existing same-PR fragments, then update/remove stale bullets or add only genuinely separate outcomes; apply the `skip-changelog` label to opt out).
|
||||
- User-visible docs changes get a `type: docs` fragment
|
||||
- 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 pr view --json number,title --jq '"PR #\\(.number): \\(.title)"'`
|
||||
- `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
|
||||
- Only Swift is the `scripts/get-mpv-window-macos.swift` helper (macOS mpv window detection); validate via `bun test scripts/get-mpv-window-macos.test.ts`
|
||||
- For CI debugging, inspect runs with `gh run list/view`; rerun or fix only within the requested scope.
|
||||
|
||||
+174
@@ -1,5 +1,179 @@
|
||||
# Changelog
|
||||
|
||||
## v0.19.6 (2026-09-04)
|
||||
|
||||
### Added
|
||||
|
||||
- **Card Timing Review**:
|
||||
- Optional pre-generation timing review for word, sentence, and audio cards, with a speech-weighted waveform that flattens background noise so dialogue edges stand out clearly.
|
||||
- The clip end automatically snaps back to where the line's dialogue actually ends once the waveform loads, with drag and keyboard adjustments available.
|
||||
- Audio preview includes a sweeping playhead that plays the clip to its true end, even on high-latency outputs like Bluetooth headphones.
|
||||
- Previous and next subtitle lines can be pulled onto the card with `P`/`N` (or the Prev/Next steppers) and removed with Shift; the sentence preview and waveform markers update automatically.
|
||||
- Cancelling lets you keep a card without media, and the review can be toggled on or off for the session.
|
||||
- **Senren Field Grouping**:
|
||||
- Enable `ankiConnect.isSenren` to merge duplicate mined cards using Senren's scene-switching markup, grouping sentence, furigana, audio, picture, and misc-info fields.
|
||||
- Supports the same auto/manual/disabled modes as Kiku, including the manual merge modal; only one of Senren or Kiku can be enabled at a time.
|
||||
|
||||
### Changed
|
||||
|
||||
- **Remote Stream Mining Performance**: Mining a card from a remote stream (Jellyfin and other HTTP sources) now downloads the clip window once and reuses it for the timing review waveform, audio preview, audio extraction, and screenshot, instead of re-fetching the stream at each step; the temporary file is cleaned up after ten minutes of inactivity or on exit.
|
||||
- **TsukiHime Release Filtering**: The TsukiHime modal's Japanese and secondary-language tabs now filter the release list by the subtitle languages each release actually carries, and report when no release has subtitles for the active tab.
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Subtitle & Mining Accuracy**:
|
||||
- Broadcast-style captions that split one sentence across two on-screen rows (e.g. Crunchyroll Japanese subs) now merge into a single line for the sidebar and mined cards, while separate speakers, sound effects, and labeled turns still stay on their own lines.
|
||||
- Mining from the overlay no longer pulls in a lingering row from the previous caption; the mined sentence and clip timing now match what's actually on screen.
|
||||
- Multi-line copy and mining now select lines backward in timeline order after seeking, instead of in playback encounter order.
|
||||
- Copying a subtitle, mining a sentence, or recording immersion stats no longer includes the separate furigana line that broadcast ASS captions place above a word.
|
||||
- **Card Update Notifications**: Dismissed lingering overlay card-update progress when notification settings switch to OSD before an update finishes.
|
||||
- **Overlay Stability on Hyprland**: Opening a modal window (timing review, Jimaku, session help, and others) while mpv is fullscreen no longer causes the overlay to flicker while the modal loads; the overlay now stays on screen untouched until the modal is ready.
|
||||
- **Jellyfin Subtitle Sync**: Jellyfin subtitle files now load with zero mpv delay instead of inferring and saving an offset from Japanese and English cue timelines.
|
||||
- **Secondary Subtitle Visibility**: Native mpv secondary subtitles stay hidden when switching secondary subtitle tracks during playback.
|
||||
|
||||
## v0.19.5 (2026-08-30)
|
||||
|
||||
### Fixed
|
||||
|
||||
- **Anki Card Update Progress**: The card-update spinner now stays visible until audio and image updates finish, instead of disappearing early.
|
||||
- **Anki Word-Card Fields**: Word-card enrichment now writes sentence text and audio to the fields configured in AnkiConnect, while the dedicated sentence-card and audio-card actions keep their existing compatible field names.
|
||||
- **Overlapping Subtitles**:
|
||||
- Subtitle lines that start while another line is still on screen now appear alongside it, instead of staying hidden until a track switch or seek.
|
||||
- Subtitles shown at the same time now stack by their authored screen position, with top signs and song lines above bottom dialogue.
|
||||
- Half-size ASS furigana is no longer shown as if it were a dialogue line.
|
||||
- **YouTube Auto Captions**:
|
||||
- Auto-generated captions now follow their intended timing and two-row roll-up layout.
|
||||
- Long speech is paged instead of covering the video with a wall of text.
|
||||
- Explicitly timed sound cues like `[音楽]` no longer cover later dialogue.
|
||||
|
||||
## v0.19.4 (2026-08-25)
|
||||
|
||||
### Added
|
||||
- **Library Merge & Move**: Duplicate library cards for the same show can now be combined. Select cards in the library grid and use "Merge Selected" to pick which entry to keep and move every episode onto it, preserving sessions, mined cards, and watch time. Episodes can also be reassigned individually via the "→" button, useful when a file lands under a stray title; manual assignments survive later filename parsing, Jellyfin refreshes, and season repair. Exact AniList title matches with compatible seasons now merge automatically, while fuzzy matches surface as dismissible "Possible duplicate" reviews instead of merging silently.
|
||||
- **Duplicate Line Cleanup Tool**: The Vocabulary tab's new "Duplicates" button scans a chosen time window (7 days through all time) for old karaoke/typeset duplicate-line bursts, shows what it found, and collapses each run to one line once confirmed; `subminer stats cleanup --duplicate-lines` does the same from the terminal, with `--dry-run` and `--lookback-days <n>` options. Watch time and lines-seen totals are left unchanged.
|
||||
|
||||
### Changed
|
||||
- **Prerelease Release Notes**: Prerelease notes now open with a "Changes since" section listing only what changed versus the previous beta/RC of the same version, above the cumulative highlights, and CI rejects prerelease tags whose committed notes were generated for a different beta/RC.
|
||||
|
||||
### Fixed
|
||||
- **Subtitle & Karaoke Duplication**:
|
||||
- Karaoke and animated signs are reconstructed once from their authored text and shown only while actually sung, with original word spacing preserved, instead of flooding the overlay, subtitle sidebar, immersion history, mining, or stats with glyph fragments, per-frame color phases, and repeated animation events.
|
||||
- Decorative layers (highlight sweeps, glow/shadow copies, symbol-font decoration, particle swarms, hidden or zero-scaled text) stay out of published text, while ordinary repeated dialogue, positioned signs, wrapped lyric rows, and multi-row CC-style blocks still display correctly.
|
||||
- Embedded subtitle tracks on network-mounted (SMB/NFS) media are extracted and parsed again instead of falling back to live-text-only, restoring karaoke reconstruction, sidebar cues, and mining for releases that only ship subtitles inside the container.
|
||||
- Secondary subtitles go through the same deduplication pipeline as primary subtitles and no longer clip display after about four lines.
|
||||
- Event-heavy karaoke files that previously stalled subtitle loading for several seconds now parse in well under a second.
|
||||
- **Character Dictionary Reliability**:
|
||||
- Generation, merged rebuilds, and imports no longer freeze the app on large dictionaries; snapshot I/O, archive building, and image/name lookup caches moved off the UI's critical path.
|
||||
- Dictionaries are reused instead of regenerated when MeCab finds no name splits.
|
||||
- Cached portraits restore correctly after the portrait index finishes loading post-tokenization.
|
||||
- Desktop progress notifications on Linux AppImage installs update in place instead of flickering, fixing a bug where the AppImage's bundled libraries broke the system notification helper.
|
||||
- **Overlay Startup & Modals**:
|
||||
- The macOS window-tracking helper targets macOS 12.0+ instead of requiring the build machine's exact macOS version, fixing crashes on older systems like Ventura that left the overlay stuck on "Overlay loading".
|
||||
- mpv IPC connection attempts time out and retry, showing an actionable error if content still isn't ready after 30 seconds.
|
||||
- Dedicated overlay modals are prewarmed on macOS and Windows so shortcuts open them promptly.
|
||||
- On macOS, reused modals and the stats window open above fullscreen mpv on its current Space instead of jumping to another desktop.
|
||||
- **Wayland File Drop**: Fixed native Wayland drag-and-drop from file managers such as Thunar, so subtitle and video files dropped on the visible overlay are resolved and forwarded to mpv.
|
||||
- **Windows Mouse Lag**: Fixed system-wide mouse lag on Windows while SubMiner is running, caused by the overlay's global mouse hook for click-through forwarding and by the mpv window tracker blocking the app on repeated PowerShell lookups.
|
||||
- **Sentence Mining Audio & Clips**: Sentence-audio generation no longer times out on slow network-mounted media with many subtitle/font streams (bounded FFmpeg probing, two-minute extraction budget, clearer error reporting), and mined audio/animated AVIF clips now capture the subtitle line that was actually mined by snapshotting the clip range at lookup time instead of reading live mpv state later.
|
||||
- **Stats Performance & Reliability**: Immersion stats storage now sets its SQLite busy timeout before WAL setup, avoiding transient lock errors under concurrent writes. Deletes in the stats dashboard no longer freeze the UI, run proportional to what's deleted instead of rebuilding full lifetime summaries, retry safely if the delete worker crashes, and no longer rescan the whole library when deleting very common words; a new index also makes large session deletes drop from minutes to milliseconds. Library merges, video moves, and AniList reassignments got the same lifetime-summary fix.
|
||||
- **Vocabulary Stats Accuracy**: Vocabulary totals and charts now count all tracked vocabulary instead of only the first page, new-word history uses corrected daily rollups (fixing legacy timestamp and time-zone issues), summary cards refresh automatically after edits to the exclusion list, and rapid exclusion edits no longer race each other.
|
||||
- **Rofi MKV Thumbnails**: Fixed missing MKV thumbnails in the Linux rofi picker when system thumbnailer registrations only advertise legacy Matroska MIME aliases.
|
||||
|
||||
<details>
|
||||
<summary>Internal changes</summary>
|
||||
|
||||
### Internal
|
||||
- Docs Site Indexing: Excluded the `/main/` and `/v/<version>/` docs trees from search indexing (self-referential canonical, `noindex,follow`, matching `X-Robots-Tag`) so crawlers focus on current docs instead of ~30 archived copies of every page, and restored `<lastmod>` dates in the docs sitemap that were silently dropped by production builds.
|
||||
|
||||
</details>
|
||||
|
||||
## v0.19.3 (2026-08-13)
|
||||
|
||||
### Added
|
||||
- Changelog Modal: Adds an in-app changelog you can open from the tray ("View Changelog") or the "What's New" button on the update notification, so the notification stays reachable while you read. It shows the newest published release notes (falling back to the bundled changelog if that fetch fails), folds older versions while keeping the current one expanded, and supports keyboard navigation (`J`/`K`/arrows, `Enter`, `R`, `Esc`).
|
||||
|
||||
### Changed
|
||||
- Subtitle Tokenization Performance: Reworks subtitle dictionary lookups to cut per-line work roughly in half, cache repeated lookups across lines, and stop tokenization from competing with on-screen subtitle prefetching. Also fixes several accuracy issues along the way: dropped readings on trailing kana, character names being skipped after a dictionary sync, annotations not refreshing after mining a card, and halfwidth katakana character names losing their reading or being swallowed by other words.
|
||||
|
||||
### Fixed
|
||||
- Character Dictionary Large Imports: Large character dictionaries (e.g. One Piece) no longer fail to install from a fixed timeout budget; the import now scales its time budget to dictionary size and reports detailed progress (page/character counts, image download progress, elapsed time) instead of one static message.
|
||||
- Stats Delete Responsiveness: Deleting sessions, episodes, or library entries no longer freezes the stats page or an active video player; deletes are now batched into a single transaction.
|
||||
- Styled Subtitle Cue Parsing: Heavily typeset subtitles (karaoke, signs) no longer flood the subtitle sidebar with garbage; vector drawing commands are no longer shown as text, and duplicate/animation-burst cues now collapse into one.
|
||||
- X11 mpv Renderer: Fixes an mpv crash on the first fullscreen toggle for X11/XWayland users with `gpu-next` shaders (e.g. ArtCNN), which was caused by X11 mode forcing the legacy OpenGL renderer.
|
||||
- X11 Overlay Display Scaling: Fixes the overlay appearing oversized and offset from mpv on X11/XWayland under fractional or mixed-monitor display scaling.
|
||||
|
||||
<details>
|
||||
<summary>Internal changes</summary>
|
||||
|
||||
### Internal
|
||||
- Subtitle text is now decoded from ASS exactly once at ingest, so the renderer, timing tracker, and tokenizer all share one decoded value instead of each re-deriving it.
|
||||
- Added per-stage debug timings (`scanMs`, `mecabMs`, `frequencyMs`, `annotateMs`) to the subtitle tokenization pipeline log.
|
||||
|
||||
</details>
|
||||
|
||||
## v0.19.2 (2026-08-04)
|
||||
|
||||
### Changed
|
||||
- Subsync: The sync modal now lets you choose both the reference subtitle (correct timing) and the out-of-sync subtitle to retime, for both alass and ffsubsync. alass can also use the loaded video's audio as a reference for local files. Retiming the secondary track now reloads the result into the secondary slot instead of overwriting the primary subtitle.
|
||||
|
||||
### Fixed
|
||||
- Streaming Subtitle Tokenization: Jellyfin streams now seed subtitle tokenization directly from the downloaded subtitle file instead of relying on an mpv event that could be missed, and prefetching now runs to the end of the file and clears between episodes. The tokenization cache was raised from 256 to 2500 lines, and parsed cues are no longer lost when the active subtitle track briefly can't be resolved (e.g. switching to an embedded track). Together these prevent episodes from falling back to slow, line-by-line tokenization during playback.
|
||||
- Overlay: Subtitle lines now appear immediately at their cue time even on a tokenization cache miss, upgrading in place once tokens and annotations are ready, instead of waiting on a line still being processed. A failed tokenization is no longer cached as plain text, so repeated lines get another chance at annotations.
|
||||
- Background Logging: Background startup now respects the configured logging level when no explicit log level is passed.
|
||||
|
||||
<details>
|
||||
<summary>Internal changes</summary>
|
||||
|
||||
### Internal
|
||||
- Patched three high-severity dependency advisories (`undici`, `brace-expansion`, `fast-uri`).
|
||||
|
||||
</details>
|
||||
|
||||
## v0.19.1 (2026-08-01)
|
||||
|
||||
### Added
|
||||
- Word Card Type: Adds a setting (Settings > Mining/Anki > Kiku/Lapis Features > "Word Card Type") to choose which card-type flag SubMiner marks on Kiku/Lapis word cards — `word-and-sentence` (default), `click`, `sentence`, `audio`, or `none`. Click cards (`IsClickCard`) can now be flagged, and setting any card-type flag clears the others so a note can't claim two types at once.
|
||||
|
||||
### Fixed
|
||||
- Yomitan Popup: Fixes the macOS Yomitan popup going inert after mining a card — clicks outside the popup no longer pass through to mpv, and scrolling over the popup scrolls its definitions instead of seeking playback.
|
||||
- YouTube Playlist Links: Fixes opening a video from a playlist URL (e.g. a Watch Later link with `list=`/`index=`) timing out while probing subtitles, metadata, or the playback URL.
|
||||
|
||||
## v0.19.0 (2026-07-29)
|
||||
|
||||
### Added
|
||||
- Anki Maturity Highlighting: Known-word subtitle highlights can now be colored by Anki card maturity (new, learning, young, mature), similar to asbplayer. Tier thresholds and colors are configurable, with a runtime toggle and an updated help legend.
|
||||
- Post-Playback Menu: After a watch-history episode ends, the fzf/rofi launcher returns to that series with options to play the previous or next episode, rewatch, pick another episode, or quit. The pre-playback series menu now offers the previous episode too.
|
||||
- Delete Library Entries: The stats Library detail view can now delete an entire title in one step (episodes, sessions, subtitle lines, rollups, cover art, and vocabulary counts). Delete progress is now shown app-wide via a progress bar and status toast instead of disappearing when you switch tabs.
|
||||
- Cross-Machine Sync: Added SSH-based syncing of stats and watch history between machines, available from the tray ("Sync Stats & History") or `subminer sync`, with saved devices, per-host sync direction, background auto-sync, connection testing, manual snapshots, and support for Windows remotes.
|
||||
- TsukiHime Subtitle Downloads: Added subtitle downloads for the current video via TsukiHime, loading Japanese as the primary track and your configured secondary language directly into mpv.
|
||||
|
||||
### Changed
|
||||
- Clipboard-Video Shortcut: The "append clipboard video to queue" shortcut is now configurable.
|
||||
|
||||
### Fixed
|
||||
- AniList Season Resolution: Season 2+ files now resolve to the correct AniList entry instead of silently falling back to season 1 (which mismatched character dictionaries and watch progress). Manual overrides now stay scoped per season, fix both the dictionary and progress tracking together, and also correct per-season cover art.
|
||||
- Subtitle Annotation Accuracy: Fixed several annotation edge cases, including inconsistent POS exclusions on merged quote-particle tokens, dropped annotations on supplementary-plane kanji, katakana punctuation wrongly treated as noise, and certain kanji vocabulary losing N+1 highlighting eligibility.
|
||||
- AnkiConnect Proxy Port Conflict: Video startup no longer crashes when another process already holds the configured AnkiConnect proxy port; a notification now explains how to resolve it.
|
||||
- AppImage Quit Crash: Fixed a "Service Crash" desktop notification appearing after closing a video when running the Linux AppImage.
|
||||
- Autoplay Pause Timing: Fixed playback resuming a few seconds before subtitle tokenization warmup finished, most noticeable when resuming mid-episode or when a cue starts within the first two seconds.
|
||||
- Stats Known-Word Count: Fixed stats reporting 0 known words for every session after the known-word cache format changed.
|
||||
- Stats Library Cover Art: Relinking a title to a different AniList entry now updates its cover in the Library grid immediately instead of leaving a stale, mismatched cover cached.
|
||||
- Rofi Prompt Spacing: Rofi menu prompts now keep a space before the input field instead of running into the placeholder text.
|
||||
- Stats Settings & Reliability: Hardened stats settings validation (nested/legacy AnkiConnect config now falls back safely instead of breaking) and stats routes against malformed requests and other edge cases.
|
||||
- Stats Delete Performance: Deleting sessions, episodes, and library entries is now dramatically faster and no longer stalls playback (e.g. a 12-episode title dropped from about a minute to under a second on a large library); the Vocabulary tab also loads much faster. The first launch after upgrading runs a one-time database migration.
|
||||
|
||||
<details>
|
||||
<summary>Internal changes</summary>
|
||||
|
||||
### Internal
|
||||
- Added a golden-file regression test corpus for the tokenizer/annotation pipeline, plus scripts to record new fixtures and diff against stock Yomitan.
|
||||
- Consolidated renderer modal state handling into a descriptor registry.
|
||||
- Consolidated CI quality checks (PR, stable, and prerelease) into one reusable workflow with mpv plugin tests and dependency audits.
|
||||
- Removed the unused stats IPC transport and unified stats dashboard HTTP types with the backend contract.
|
||||
- Added a script to verify known-word highlight tiers against live Anki data outside of playback.
|
||||
|
||||
</details>
|
||||
|
||||
## v0.18.0 (2026-07-10)
|
||||
|
||||
### Added
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
APP_NAME := subminer
|
||||
THEME_SOURCE := assets/themes/subminer.rasi
|
||||
THUMBNAILER_SOURCE := assets/thumbnailers/subminer-ffmpegthumbnailer.thumbnailer
|
||||
LAUNCHER_OUT := dist/launcher/$(APP_NAME)
|
||||
THEME_FILE := subminer.rasi
|
||||
THUMBNAILER_FILE := subminer-ffmpegthumbnailer.thumbnailer
|
||||
|
||||
# Default install prefix for the wrapper script.
|
||||
PREFIX ?= $(HOME)/.local
|
||||
@@ -158,14 +160,8 @@ build-macos-unsigned: deps
|
||||
@bun run build:mac:unsigned
|
||||
|
||||
build-launcher:
|
||||
@printf '%s\n' "[INFO] Bundling launcher script"
|
||||
@install -d "$(dir $(LAUNCHER_OUT))"
|
||||
@bun build ./launcher/main.ts --target=bun --packages=bundle --outfile="$(LAUNCHER_OUT)"
|
||||
@if ! head -1 "$(LAUNCHER_OUT)" | grep -q '^#!/usr/bin/env bun'; then \
|
||||
{ printf '#!/usr/bin/env bun\n'; cat "$(LAUNCHER_OUT)"; } > "$(LAUNCHER_OUT).tmp" && mv "$(LAUNCHER_OUT).tmp" "$(LAUNCHER_OUT)"; \
|
||||
fi
|
||||
@chmod +x "$(LAUNCHER_OUT)"
|
||||
@printf '%s\n' "[INFO] Launcher artifact: $(LAUNCHER_OUT)"
|
||||
@printf '%s\n' "[INFO] Building launcher runtime artifacts"
|
||||
@bun run build:launcher
|
||||
|
||||
clean:
|
||||
@printf '%s\n' "[INFO] Removing build artifacts"
|
||||
@@ -221,11 +217,13 @@ docs-dev: ensure-bun
|
||||
|
||||
|
||||
install-linux: build-launcher
|
||||
@printf '%s\n' "[INFO] Installing Linux wrapper/theme artifacts"
|
||||
@printf '%s\n' "[INFO] Installing Linux wrapper/support artifacts"
|
||||
@install -d "$(BINDIR)"
|
||||
@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)/thumbnailers"
|
||||
@install -m 0644 "./$(THUMBNAILER_SOURCE)" "$(LINUX_DATA_DIR)/thumbnailers/$(THUMBNAILER_FILE)"
|
||||
@install -d "$(LINUX_DATA_DIR)/plugin/subminer"
|
||||
@cp -R ./plugin/subminer/. "$(LINUX_DATA_DIR)/plugin/subminer/"
|
||||
@if [ -n "$(APPIMAGE_SRC)" ]; then \
|
||||
@@ -234,7 +232,7 @@ install-linux: build-launcher
|
||||
printf '%s\n' "[WARN] No release/SubMiner-*.AppImage found; skipping AppImage install"; \
|
||||
printf '%s\n' " Build one with: make build"; \
|
||||
fi
|
||||
@printf '%s\n' "Installed to:" " $(BINDIR)/subminer" " $(LINUX_DATA_DIR)/themes/$(THEME_FILE)"
|
||||
@printf '%s\n' "Installed to:" " $(BINDIR)/subminer" " $(LINUX_DATA_DIR)/themes/$(THEME_FILE)" " $(LINUX_DATA_DIR)/thumbnailers/$(THUMBNAILER_FILE)"
|
||||
|
||||
install-macos: build-launcher
|
||||
@printf '%s\n' "[INFO] Installing macOS wrapper/theme/app artifacts"
|
||||
@@ -275,8 +273,9 @@ uninstall:
|
||||
uninstall-linux:
|
||||
@rm -f "$(BINDIR)/subminer" "$(BINDIR)/SubMiner.AppImage"
|
||||
@rm -f "$(LINUX_DATA_DIR)/themes/$(THEME_FILE)"
|
||||
@rm -f "$(LINUX_DATA_DIR)/thumbnailers/$(THUMBNAILER_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"
|
||||
@printf '%s\n' "Removed:" " $(BINDIR)/subminer" " $(BINDIR)/SubMiner.AppImage" " $(LINUX_DATA_DIR)/themes/$(THEME_FILE)" " $(LINUX_DATA_DIR)/thumbnailers/$(THUMBNAILER_FILE)" " $(LINUX_DATA_DIR)/plugin/subminer"
|
||||
|
||||
uninstall-macos:
|
||||
@rm -f "$(BINDIR)/subminer"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
# SubMiner
|
||||
|
||||
Integrates Yomitan and mpv - on-screen lookups, mine to Anki, and track immersion without leaving the player
|
||||
Integrates Yomitan or Hachidori with mpv - on-screen lookups, mine to Anki, and track immersion without leaving the player
|
||||
|
||||
[Installation](#quick-start) · [Requirements](#requirements) · [Usage](https://docs.subminer.moe/usage) · [Documentation](https://docs.subminer.moe)
|
||||
|
||||
@@ -15,7 +15,7 @@ Integrates Yomitan and mpv - on-screen lookups, mine to Anki, and track immersio
|
||||
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||
[](https://www.typescriptlang.org)
|
||||
|
||||
[](https://github.com/user-attachments/assets/89e61895-e2b7-4b47-8d50-a35afe4132b2)
|
||||
[](https://github.com/user-attachments/assets/7abab8a9-4e4e-4f06-9f3c-9783e15a3807)
|
||||
|
||||
</div>
|
||||
|
||||
@@ -25,6 +25,8 @@ Integrates Yomitan and mpv - on-screen lookups, mine to Anki, and track immersio
|
||||
|
||||
Hover over any word and trigger a lookup to get the full Yomitan popup - definitions, pitch accent, and frequency data - without ever leaving mpv.
|
||||
|
||||
Yomitan remains the default. Select the bundled Hachidori backend with `dictionaryBackend: "hachidori"` and restart SubMiner. The tray opens the selected backend's settings. See [dictionary setup](https://docs.subminer.moe/usage#hachidori-setup) for importing dictionaries, linking an external Hachidori host, and configuring Anki.
|
||||
|
||||
<div align="center">
|
||||
<img src="docs-site/public/screenshots/yomitan-lookup.png" width="800" alt="Yomitan dictionary popup over annotated subtitles in mpv">
|
||||
</div>
|
||||
@@ -90,6 +92,10 @@ Browse sibling episode files and the active mpv queue in one overlay modal. Open
|
||||
<td><b>Jimaku</b></td>
|
||||
<td>Search and download Japanese subtitles</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Local Subtitle Generation</b></td>
|
||||
<td>Generate Japanese subtitles from local audio in a standalone modal (<code>Ctrl+Shift+G</code>), the sidebar button, or launcher, with progress and optional managed model downloads. Requires whisper.cpp and FFmpeg. Optional Silero speech detection prioritizes dialogue in separately timed passages. <a href="https://docs.subminer.moe/main/subtitle-generation">Setup guide</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>TsukiHime</b></td>
|
||||
<td>Search and download subtitles extracted from anime releases, with Japanese and secondary-language tabs (<code>Ctrl+Shift+T</code>) — no API key, requires <code>xz</code> on your <code>PATH</code></td>
|
||||
@@ -191,7 +197,9 @@ wget https://github.com/ksyasuda/SubMiner/releases/latest/download/SubMiner.AppI
|
||||
&& chmod +x ~/.local/bin/SubMiner.AppImage
|
||||
```
|
||||
|
||||
The AppImage is all you need. The optional `subminer` command-line launcher runs on [Bun](https://bun.sh), and first-run setup can install both for you. To grab it manually instead, install Bun first, then:
|
||||
The AppImage is all you need. First-run setup can install the optional `subminer` command-line launcher. Every current launcher uses Bun included with the app, so you do not need Bun installed or on `PATH`.
|
||||
|
||||
You can also download the launcher wrapper directly:
|
||||
|
||||
```bash
|
||||
wget https://github.com/ksyasuda/SubMiner/releases/latest/download/subminer -O ~/.local/bin/subminer \
|
||||
@@ -212,6 +220,8 @@ Download the latest DMG from [GitHub Releases](https://github.com/ksyasuda/SubMi
|
||||
|
||||
Download and run the latest installer (`.exe`) from [GitHub Releases](https://github.com/ksyasuda/SubMiner/releases/latest).
|
||||
|
||||
For terminal use, download `subminer.cmd`. It locates the installed app and uses its private Bun runtime.
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
@@ -223,14 +233,14 @@ See the [build-from-source guide](https://docs.subminer.moe/installation#from-so
|
||||
|
||||
### 2. Launch & Set Up
|
||||
|
||||
Run SubMiner and the first-run setup wizard will guide you through importing Yomitan dictionaries and optionally installing the `subminer` command-line launcher.
|
||||
Run the installed app and the first-run setup wizard will guide you through importing Yomitan dictionaries and optionally installing the `subminer` command-line launcher. Setup records a custom app location when needed, and the wrapper runs with the app's private Bun runtime.
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
subminer app --setup
|
||||
~/.local/bin/SubMiner.AppImage --setup
|
||||
|
||||
# macOS — open SubMiner.app, or:
|
||||
subminer app --setup
|
||||
# macOS
|
||||
open -a SubMiner --args --setup
|
||||
```
|
||||
|
||||
On **Windows**, just run `SubMiner.exe` and the setup will open automatically on first launch.
|
||||
@@ -262,13 +272,17 @@ SubMiner builds on the work of these open-source projects:
|
||||
| [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 |
|
||||
| [Bun](https://github.com/oven-sh/bun) | Bundled runtime for the `subminer` command-line launcher |
|
||||
| [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](https://github.com/yomidevs/yomitan) | Default dictionary engine and morphological parser |
|
||||
| [Hachidori](https://github.com/bee-san/hachidori) | Alternative dictionary backend, powered by HoshiDicts |
|
||||
| [yomitan-jlpt-vocab](https://github.com/stephenmk/yomitan-jlpt-vocab) | JLPT level tags for vocabulary |
|
||||
|
||||
## License
|
||||
|
||||
[GNU General Public License v3.0](LICENSE)
|
||||
SubMiner is released under the [GNU General Public License v3.0](LICENSE).
|
||||
|
||||
Release packages also bundle an unmodified copy of [Bun](https://github.com/oven-sh/bun), which is MIT licensed and statically links JavaScriptCore (LGPL 2.0) and TinyCC (LGPL 2.1). Its license texts and third-party notices ship inside the app under `resources/bun/licenses`, and each release publishes `bun-v1.3.5-source.tar.gz` with the corresponding source. See [Bundled Bun runtime](https://docs.subminer.moe/installation#bundled-bun-runtime).
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 114 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 23 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 303 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 3.0 MiB After Width: | Height: | Size: 3.8 MiB |
@@ -0,0 +1,4 @@
|
||||
[Thumbnailer Entry]
|
||||
TryExec=ffmpegthumbnailer
|
||||
Exec=ffmpegthumbnailer -i %i -o %o -s %s -f
|
||||
MimeType=video/matroska;video/matroska-3d;video/x-matroska;video/x-matroska-3d;
|
||||
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"version": "1.3.5",
|
||||
"bunRevision": "1e86cebd74a5723e818b5c0555276b646bcf0e4c",
|
||||
"releaseTagCommit": "fa5a5bbe556a4bda5bde77b4013aa6c3bb4ec9ab",
|
||||
"artifacts": {
|
||||
"darwin-arm64": {
|
||||
"file": "bun-darwin-aarch64.zip",
|
||||
"sha256": "db17588a4aea8804856825d4bead3f05e1f37276ca606f37e369b4f72f35d3fb"
|
||||
},
|
||||
"darwin-x64": {
|
||||
"file": "bun-darwin-x64-baseline.zip",
|
||||
"sha256": "34b9a56b851058dafa1bc9d61233f2c383aa996889bba30b3180f5ccc2cff1b2"
|
||||
},
|
||||
"linux-arm64": {
|
||||
"file": "bun-linux-aarch64.zip",
|
||||
"sha256": "ed01000f85bd97785228ad2845dc92a1860b8054856826d7317690ac8f8ee74b"
|
||||
},
|
||||
"linux-x64": {
|
||||
"file": "bun-linux-x64-baseline.zip",
|
||||
"sha256": "6bddacd6a65855698b9816f2d74871eda4dd0b7fa921140c6445248f94a742fd"
|
||||
},
|
||||
"win32-x64": {
|
||||
"file": "bun-windows-x64-baseline.zip",
|
||||
"sha256": "bf447dcc3b06aba9b9706a9db46fcd65e06a4d47d31439922313825d06eb47ca"
|
||||
}
|
||||
},
|
||||
"licenseInventoryStatus": "source-and-notices-pinned-to-binary-revision",
|
||||
"sourceManifest": "build/bun-source-manifest.json",
|
||||
"correspondingSourceAsset": "bun-v1.3.5-source.tar.gz"
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"version": "1.3.5",
|
||||
"releaseTag": "bun-v1.3.5",
|
||||
"releaseTagCommit": "fa5a5bbe556a4bda5bde77b4013aa6c3bb4ec9ab",
|
||||
"bunRevision": "1e86cebd74a5723e818b5c0555276b646bcf0e4c",
|
||||
"archiveName": "bun-v1.3.5-source.tar.gz",
|
||||
"sources": [
|
||||
{
|
||||
"name": "bun",
|
||||
"repository": "oven-sh/bun",
|
||||
"revision": "1e86cebd74a5723e818b5c0555276b646bcf0e4c",
|
||||
"destination": "bun",
|
||||
"sha256": "3a766087902a62a4920e5ac5452dbc7143ff97ea19721f945146629fc9f2d82c",
|
||||
"licensePaths": ["LICENSE.md"]
|
||||
},
|
||||
{
|
||||
"name": "WebKit",
|
||||
"repository": "oven-sh/WebKit",
|
||||
"revision": "6d0f3aac0b817cc01a846b3754b21271adedac12",
|
||||
"destination": "bun/vendor/WebKit",
|
||||
"transport": "git-sparse",
|
||||
"exclude": ["JSTests", "LayoutTests", "ManualTests", "PerformanceTests", "WebDriverTests"],
|
||||
"licensePaths": ["Source/JavaScriptCore/COPYING.LIB"]
|
||||
},
|
||||
{
|
||||
"name": "boringssl",
|
||||
"repository": "oven-sh/boringssl",
|
||||
"revision": "f1ffd9e83d4f5c28a9c70d73f9a4e6fcf310062f",
|
||||
"destination": "bun/vendor/boringssl",
|
||||
"sha256": "af8fd325793bb261c70114d09a24f36092fbe252be8ec040f0054ec9455f0ea3",
|
||||
"licensePaths": ["LICENSE"]
|
||||
},
|
||||
{
|
||||
"name": "brotli",
|
||||
"repository": "google/brotli",
|
||||
"revision": "ed738e842d2fbdf2d6459e39267a633c4a9b2f5d",
|
||||
"upstreamReference": "v1.1.0",
|
||||
"destination": "bun/vendor/brotli",
|
||||
"sha256": "aaa739962a45b508b2e783b915e6b2b57ed3b12bd4b0feac73acfb144dffa54f",
|
||||
"licensePaths": ["LICENSE"]
|
||||
},
|
||||
{
|
||||
"name": "cares",
|
||||
"repository": "c-ares/c-ares",
|
||||
"revision": "3ac47ee46edd8ea40370222f91613fc16c434853",
|
||||
"destination": "bun/vendor/cares",
|
||||
"sha256": "8c94116cb366ae4a44e487da4d9f7e736287d329efa6f88fdf077cd2d0a2e4b8",
|
||||
"licensePaths": ["LICENSE.md"]
|
||||
},
|
||||
{
|
||||
"name": "hdrhistogram",
|
||||
"repository": "HdrHistogram/HdrHistogram_c",
|
||||
"revision": "be60a9987ee48d0abf0d7b6a175bad8d6c1585d1",
|
||||
"destination": "bun/vendor/hdrhistogram",
|
||||
"sha256": "811c5e5ae5303a75ade50688880af6aad5d2f951ec5785f68186bd18635cdfc9",
|
||||
"licensePaths": ["COPYING.txt", "LICENSE.txt"]
|
||||
},
|
||||
{
|
||||
"name": "highway",
|
||||
"repository": "google/highway",
|
||||
"revision": "ac0d5d297b13ab1b89f48484fc7911082d76a93f",
|
||||
"destination": "bun/vendor/highway",
|
||||
"sha256": "a7a816f4b62a0414ff0d39c0a8875847468dd6f7c9ad71781a24469a756cdea7",
|
||||
"licensePaths": ["LICENSE"]
|
||||
},
|
||||
{
|
||||
"name": "libarchive",
|
||||
"repository": "libarchive/libarchive",
|
||||
"revision": "9525f90ca4bd14c7b335e2f8c84a4607b0af6bdf",
|
||||
"destination": "bun/vendor/libarchive",
|
||||
"sha256": "944db9ab58a3cbdb5d947db4f04a3cc15f83b3147fcf7830ada592ba8d4a102c",
|
||||
"licensePaths": ["COPYING"]
|
||||
},
|
||||
{
|
||||
"name": "libdeflate",
|
||||
"repository": "ebiggers/libdeflate",
|
||||
"revision": "c8c56a20f8f621e6a966b716b31f1dedab6a41e3",
|
||||
"destination": "bun/vendor/libdeflate",
|
||||
"sha256": "1e5cc06bdbf3e1245d8b89c9e3588f507e3c8bc53fe8b8229770a9e8661dea81",
|
||||
"licensePaths": ["COPYING"]
|
||||
},
|
||||
{
|
||||
"name": "libuv",
|
||||
"repository": "libuv/libuv",
|
||||
"revision": "f3ce527ea940d926c40878ba5de219640c362811",
|
||||
"destination": "bun/vendor/libuv",
|
||||
"sha256": "46040d51e8aa86a7c84e377e224d427c35022ec6e3ed8ed399493b0d8574d0cf",
|
||||
"licensePaths": ["LICENSE"]
|
||||
},
|
||||
{
|
||||
"name": "lolhtml",
|
||||
"repository": "cloudflare/lol-html",
|
||||
"revision": "d64457d9ff0143deef025d5df7e8586092b9afb7",
|
||||
"destination": "bun/vendor/lolhtml",
|
||||
"sha256": "893b77b460f4c4f4634c973f72cf35e38ebe30646d14a92db3f50ee7e586c0ad",
|
||||
"licensePaths": ["LICENSE"]
|
||||
},
|
||||
{
|
||||
"name": "lshpack",
|
||||
"repository": "litespeedtech/ls-hpack",
|
||||
"revision": "8905c024b6d052f083a3d11d0a169b3c2735c8a1",
|
||||
"destination": "bun/vendor/lshpack",
|
||||
"sha256": "07d8bf901bb1b15543f38eabd23938519e1210eebadb52f3d651d6ef130ef973",
|
||||
"licensePaths": ["LICENSE"]
|
||||
},
|
||||
{
|
||||
"name": "mimalloc",
|
||||
"repository": "oven-sh/mimalloc",
|
||||
"revision": "1beadf9651a7bfdec6b5367c380ecc3fe1c40d1a",
|
||||
"destination": "bun/vendor/mimalloc",
|
||||
"sha256": "317ec2a83462ece78c344c4955c6fee103b412a8e6a0d6ddf9ec8963ed9c0881",
|
||||
"licensePaths": ["LICENSE"]
|
||||
},
|
||||
{
|
||||
"name": "picohttpparser",
|
||||
"repository": "h2o/picohttpparser",
|
||||
"revision": "066d2b1e9ab820703db0837a7255d92d30f0c9f5",
|
||||
"destination": "bun/vendor/picohttpparser",
|
||||
"sha256": "637ff2ab6f5c7f7e05a5b5dc393d5cf2fea8d4754fcaceaaf935ffff5c1323ee",
|
||||
"licensePaths": ["picohttpparser.c", "picohttpparser.h"]
|
||||
},
|
||||
{
|
||||
"name": "tinycc",
|
||||
"repository": "oven-sh/tinycc",
|
||||
"revision": "29985a3b59898861442fa3b43f663fc1af2591d7",
|
||||
"destination": "bun/vendor/tinycc",
|
||||
"sha256": "813cc09aafd6cea9c1ae6b745781e6b27576a0c103f49b63b5c8a732bbe3d290",
|
||||
"licensePaths": ["COPYING"]
|
||||
},
|
||||
{
|
||||
"name": "zlib",
|
||||
"repository": "cloudflare/zlib",
|
||||
"revision": "886098f3f339617b4243b286f5ed364b9989e245",
|
||||
"destination": "bun/vendor/zlib",
|
||||
"sha256": "14bf449df8308696af52f87de88f54c9eb9c91ec5e280587f9d37f7a3f6ed9bb",
|
||||
"licensePaths": ["LICENSE"]
|
||||
},
|
||||
{
|
||||
"name": "zstd",
|
||||
"repository": "facebook/zstd",
|
||||
"revision": "f8745da6ff1ad1e7bab384bd1f9d742439278e99",
|
||||
"destination": "bun/vendor/zstd",
|
||||
"sha256": "4b0bd1f0cfb25e61b9103c35f27395530ff5b4c0d2513a00fd745849e85ea52c",
|
||||
"licensePaths": ["COPYING", "LICENSE"]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -18,15 +18,16 @@
|
||||
"ws": "^8.21.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron/asar": "3.4.1",
|
||||
"@types/node": "^24.10.0",
|
||||
"@types/ws": "^8.18.1",
|
||||
"electron": "42.6.0",
|
||||
"electron-builder": "26.15.3",
|
||||
"electron-builder": "26.16.1",
|
||||
"esbuild": "^0.25.12",
|
||||
"eslint": "^10.8.0",
|
||||
"prettier": "^3.8.1",
|
||||
"typescript": "^5.9.3",
|
||||
"undici": "7.28.0",
|
||||
"undici": "7.29.0",
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -34,18 +35,20 @@
|
||||
"@discordjs/rest@2.6.1": "patches/@discordjs%2Frest@2.6.1.patch",
|
||||
},
|
||||
"overrides": {
|
||||
"@xmldom/xmldom": "0.8.13",
|
||||
"app-builder-lib": "26.15.3",
|
||||
"brace-expansion": "5.0.8",
|
||||
"electron-builder-squirrel-windows": "26.15.3",
|
||||
"@xmldom/xmldom": "0.8.15",
|
||||
"app-builder-lib": "26.16.1",
|
||||
"brace-expansion": "5.0.9",
|
||||
"electron-builder-squirrel-windows": "26.16.1",
|
||||
"fast-uri": "3.1.6",
|
||||
"form-data": "4.0.6",
|
||||
"ip-address": "10.2.0",
|
||||
"js-yaml": "4.3.0",
|
||||
"js-yaml": "4.3.2",
|
||||
"lodash": "4.18.0",
|
||||
"minimatch": "10.2.5",
|
||||
"picomatch": "4.0.4",
|
||||
"tar": "7.5.21",
|
||||
"tmp": "0.2.7",
|
||||
"undici": "7.29.0",
|
||||
},
|
||||
"packages": {
|
||||
"@discordjs/collection": ["@discordjs/collection@2.1.1", "", {}, "sha512-LiSusze9Tc7qF03sLCujF5iZp7K+vRNEDBZ86FT9aQAv3vxMLihUvKvpsCWiQ2DJq1tVckopKm1rxomgNUc9hg=="],
|
||||
@@ -178,7 +181,7 @@
|
||||
|
||||
"@neon-rs/load": ["@neon-rs/load@0.0.4", "", {}, "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw=="],
|
||||
|
||||
"@noble/hashes": ["@noble/hashes@2.2.0", "", {}, "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg=="],
|
||||
"@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="],
|
||||
|
||||
"@peculiar/asn1-schema": ["@peculiar/asn1-schema@2.8.0", "", { "dependencies": { "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.10", "tslib": "^2.8.1" } }, "sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q=="],
|
||||
|
||||
@@ -224,7 +227,7 @@
|
||||
|
||||
"@xhayper/discord-rpc": ["@xhayper/discord-rpc@1.3.4", "", { "dependencies": { "@discordjs/rest": "^2.6.1", "@vladfrangu/async_event_emitter": "^2.4.7", "discord-api-types": "^0.38.47", "ws": "^8.20.0" } }, "sha512-ff0uEXuibh9wi+l4vOj7xInLUjtlTaQBje/SCyQkeXZ0j2V0y+Zge5PQIQFRHH9TjjGaYJkTofEcQhncM2q7/w=="],
|
||||
|
||||
"@xmldom/xmldom": ["@xmldom/xmldom@0.8.13", "", {}, "sha512-KRYzxepc14G/CEpEGc3Yn+JKaAeT63smlDr+vjB8jRfgTBBI9wRj/nkQEO+ucV8p8I9bfKLWp37uHgFrbntPvw=="],
|
||||
"@xmldom/xmldom": ["@xmldom/xmldom@0.8.15", "", {}, "sha512-/5NV/vDALVFDXgLmfsy9TRCBlKwO2LNBFzpzvb9iIj+jR+eSc6DLYYvVOdivT/jm7MtU6TebYuRmzEOI7w40UA=="],
|
||||
|
||||
"abbrev": ["abbrev@4.0.0", "", {}, "sha512-a1wflyaL0tHtJSmLSOVybYhy22vRih4eduhhrkcjgrWGnRfrZtovJ2FRjxuTtkkj47O/baf0R86QU5OuYpz8fA=="],
|
||||
|
||||
@@ -240,7 +243,7 @@
|
||||
|
||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"app-builder-lib": ["app-builder-lib@26.15.3", "", { "dependencies": { "@electron/asar": "3.4.1", "@electron/fuses": "^1.8.0", "@electron/get": "^3.0.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.3", "@electron/rebuild": "^4.0.4", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", "@noble/hashes": "^2.2.0", "@peculiar/webcrypto": "^1.7.1", "@types/fs-extra": "9.0.13", "ajv": "^8.18.0", "asn1js": "^3.0.10", "async-exit-hook": "^2.0.1", "builder-util": "26.15.3", "builder-util-runtime": "9.7.0", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "26.15.3", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", "jiti": "^2.4.2", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.2.5", "pkijs": "^3.4.0", "plist": "3.1.0", "proper-lockfile": "^4.1.2", "resedit": "^1.7.0", "semver": "~7.7.3", "tar": "^7.5.7", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0", "unzipper": "^0.12.3", "which": "^5.0.0" }, "peerDependencies": { "dmg-builder": "26.15.3", "electron-builder-squirrel-windows": "26.15.3" } }, "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow=="],
|
||||
"app-builder-lib": ["app-builder-lib@26.16.1", "", { "dependencies": { "@electron/asar": "3.4.1", "@electron/fuses": "^1.8.0", "@electron/get": "^3.0.0", "@electron/notarize": "2.5.0", "@electron/osx-sign": "1.3.3", "@electron/rebuild": "^4.0.4", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", "@noble/hashes": "^1.8.0", "@peculiar/webcrypto": "^1.7.1", "@types/fs-extra": "9.0.13", "ajv": "^8.18.0", "asn1js": "^3.0.10", "async-exit-hook": "^2.0.1", "builder-util": "26.16.0", "builder-util-runtime": "9.7.0", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", "debug": "^4.3.4", "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", "electron-publish": "26.16.0", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", "jiti": "^2.4.2", "js-yaml": "^4.1.0", "json5": "^2.2.3", "lazy-val": "^1.0.5", "minimatch": "^10.2.5", "pkijs": "^3.4.0", "plist": "3.1.0", "proper-lockfile": "^4.1.2", "resedit": "^1.7.0", "semver": "~7.7.3", "tar": "^7.5.7", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0", "unzipper": "^0.12.3", "which": "^5.0.0" }, "peerDependencies": { "dmg-builder": "26.16.1", "electron-builder-squirrel-windows": "26.16.1" } }, "sha512-FhaO6YOup01ZfQW0Z6gt3AyukJjv1gW4uFK47jTgwcHZKqyN/fSlK2LqPf9tAeZYLP2bRJLDzeOkRImsw2X4Pg=="],
|
||||
|
||||
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
|
||||
|
||||
@@ -266,11 +269,11 @@
|
||||
|
||||
"boolean": ["boolean@3.2.0", "", {}, "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw=="],
|
||||
|
||||
"brace-expansion": ["brace-expansion@5.0.8", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg=="],
|
||||
"brace-expansion": ["brace-expansion@5.0.9", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg=="],
|
||||
|
||||
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
|
||||
|
||||
"builder-util": ["builder-util@26.15.3", "", { "dependencies": { "@types/debug": "^4.1.6", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "cross-spawn": "^7.0.6", "debug": "^4.3.4", "fs-extra": "^10.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "js-yaml": "^4.1.0", "sanitize-filename": "^1.6.3", "source-map-support": "^0.5.19", "stat-mode": "^1.0.0", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0" } }, "sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw=="],
|
||||
"builder-util": ["builder-util@26.16.0", "", { "dependencies": { "@types/debug": "^4.1.6", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "cross-spawn": "^7.0.6", "debug": "^4.3.4", "fs-extra": "^10.1.0", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.0", "js-yaml": "^4.1.0", "sanitize-filename": "^1.6.3", "source-map-support": "^0.5.19", "stat-mode": "^1.0.0", "temp-file": "^3.4.0", "tiny-async-pool": "1.3.0" } }, "sha512-RLyJhB7Si3YkzKR9ubQslWuXW3Vhs3CGe1i+SeixBZ0qTd1mk3XBmssvY22TlB6CS5blyko8Gu1JzpYk8UkYAg=="],
|
||||
|
||||
"builder-util-runtime": ["builder-util-runtime@9.7.0", "", { "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" } }, "sha512-g/kR520giAFYkSXTzcmF3kqQq7wi8F6N6SzeDgZrqTBN+VHdmgWOyTdD1yD7AATDId/yXLvuP34CxW46/BwCdw=="],
|
||||
|
||||
@@ -332,7 +335,7 @@
|
||||
|
||||
"discord-api-types": ["discord-api-types@0.38.49", "", {}, "sha512-XnqcWmnFZFAE8ZM8SHAw9DIV8D3Or00rMQ8iQLotrEA2PmXhl+ykaf6L6q4l474hrSUH1JaYcv+iOMRWp2p6Tg=="],
|
||||
|
||||
"dmg-builder": ["dmg-builder@26.15.3", "", { "dependencies": { "app-builder-lib": "26.15.3", "builder-util": "26.15.3", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0" } }, "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ=="],
|
||||
"dmg-builder": ["dmg-builder@26.16.1", "", { "dependencies": { "app-builder-lib": "26.16.1", "builder-util": "26.16.0", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0" } }, "sha512-pnI/3Qb24Uk+rMTgIUrsVUKosVgwmBUdF8Zeb8TexOSbpq8MWc7v6l+n+FrEqVkjNZwzBN+XpDS9ENgZ/rkWAw=="],
|
||||
|
||||
"dotenv": ["dotenv@16.6.1", "", {}, "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow=="],
|
||||
|
||||
@@ -346,11 +349,11 @@
|
||||
|
||||
"electron": ["electron@42.6.0", "", { "dependencies": { "@electron-internal/extract-zip": "^1.0.1", "@electron/get": "^5.0.0", "@types/node": "^24.9.0" }, "bin": { "electron": "cli.js", "install-electron": "install.js" } }, "sha512-axGNgd+yCTg+vi1VEGrQqAj9WVWkePKwbICSAvMiT2eTaxhij9a/xhBHD6rXV8wrlW9ZfJzE5+xg752ImxrmTw=="],
|
||||
|
||||
"electron-builder": ["electron-builder@26.15.3", "", { "dependencies": { "app-builder-lib": "26.15.3", "builder-util": "26.15.3", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.15.3", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "./cli.js", "install-app-deps": "./install-app-deps.js" } }, "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA=="],
|
||||
"electron-builder": ["electron-builder@26.16.1", "", { "dependencies": { "app-builder-lib": "26.16.1", "builder-util": "26.16.0", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", "dmg-builder": "26.16.1", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", "yargs": "^17.6.2" }, "bin": { "electron-builder": "./cli.js", "install-app-deps": "./install-app-deps.js" } }, "sha512-LrLK65QX5PUYYODXqp23FKrV7CILTtVY7mrJckNknO9jLNSMiqFkKbSMiDRw4CjOADMPVDdWLxY4mezOZWswxg=="],
|
||||
|
||||
"electron-builder-squirrel-windows": ["electron-builder-squirrel-windows@26.15.3", "", { "dependencies": { "app-builder-lib": "26.15.3", "builder-util": "26.15.3", "electron-winstaller": "5.4.0" } }, "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA=="],
|
||||
"electron-builder-squirrel-windows": ["electron-builder-squirrel-windows@26.16.1", "", { "dependencies": { "app-builder-lib": "26.16.1", "builder-util": "26.16.0", "electron-winstaller": "5.4.0" } }, "sha512-w0y44wSaT1l6R7CAGmeHn4nHPfvzDyCAU1xJyi1w9SbPYJpYn76SmHDzqHf8Y7l91cPWTdPYBpGQtB2T5mJ08A=="],
|
||||
|
||||
"electron-publish": ["electron-publish@26.15.3", "", { "dependencies": { "@types/fs-extra": "^9.0.11", "aws4": "^1.13.2", "builder-util": "26.15.3", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "form-data": "^4.0.5", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" } }, "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q=="],
|
||||
"electron-publish": ["electron-publish@26.16.0", "", { "dependencies": { "@types/fs-extra": "^9.0.11", "aws4": "^1.13.2", "builder-util": "26.16.0", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "form-data": "^4.0.5", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "mime": "^2.5.2" } }, "sha512-Vt3KzQIiw9BImvNOYtndg9Mjki+tl4+1sQiC/+G5j8khWaENOJFWodiB+sUl6yyHwtd37avehskdtPw7f8y/+Q=="],
|
||||
|
||||
"electron-updater": ["electron-updater@6.8.9", "", { "dependencies": { "builder-util-runtime": "9.7.0", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", "lodash.escaperegexp": "^4.1.2", "lodash.isequal": "^4.5.0", "semver": "~7.7.3", "tiny-typed-emitter": "^2.1.0" } }, "sha512-ZhVxM9iGONUpZGI1FxdMRgJjUFXi7AYGVa5PwKlO1tV1/4zDxQmfKpXOHVztKrd6L9rLcFjERvi1Mf2vxyTkig=="],
|
||||
|
||||
@@ -404,7 +407,7 @@
|
||||
|
||||
"fast-levenshtein": ["fast-levenshtein@2.0.6", "", {}, "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="],
|
||||
|
||||
"fast-uri": ["fast-uri@3.1.4", "", {}, "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw=="],
|
||||
"fast-uri": ["fast-uri@3.1.6", "", {}, "sha512-7Ical1vFEMr0onbVzEDIreM22I4khW+fzyQPwvAFWBp1iwdshSZRsL4jjRvPG9JP1uiqMHRto+YU6R2/CzDz5Q=="],
|
||||
|
||||
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
|
||||
|
||||
@@ -496,7 +499,7 @@
|
||||
|
||||
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
|
||||
|
||||
"js-yaml": ["js-yaml@4.3.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q=="],
|
||||
"js-yaml": ["js-yaml@4.3.2", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA=="],
|
||||
|
||||
"json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
|
||||
|
||||
@@ -714,7 +717,7 @@
|
||||
|
||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
||||
|
||||
"undici": ["undici@7.28.0", "", {}, "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA=="],
|
||||
"undici": ["undici@7.29.0", "", {}, "sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw=="],
|
||||
|
||||
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||
|
||||
@@ -754,8 +757,6 @@
|
||||
|
||||
"@discordjs/rest/discord-api-types": ["discord-api-types@0.38.43", "", {}, "sha512-sSoBf/nK6m7BGtw65mi+QBuvEWaHE8MMziFLqWL+gT6ME/BLg34dRSVKS3Husx40uU06bvxUc3/X+D9Y6/zAbw=="],
|
||||
|
||||
"@discordjs/rest/undici": ["undici@6.27.0", "", {}, "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg=="],
|
||||
|
||||
"@discordjs/util/discord-api-types": ["discord-api-types@0.38.43", "", {}, "sha512-sSoBf/nK6m7BGtw65mi+QBuvEWaHE8MMziFLqWL+gT6ME/BLg34dRSVKS3Husx40uU06bvxUc3/X+D9Y6/zAbw=="],
|
||||
|
||||
"@electron/asar/commander": ["commander@5.1.0", "", {}, "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg=="],
|
||||
@@ -808,8 +809,6 @@
|
||||
|
||||
"node-gyp/env-paths": ["env-paths@2.2.1", "", {}, "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A=="],
|
||||
|
||||
"node-gyp/undici": ["undici@6.27.0", "", {}, "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg=="],
|
||||
|
||||
"node-gyp/which": ["which@6.0.1", "", { "dependencies": { "isexe": "^4.0.0" }, "bin": { "node-which": "bin/which.js" } }, "sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="],
|
||||
|
||||
"pkijs/@noble/hashes": ["@noble/hashes@1.4.0", "", {}, "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg=="],
|
||||
|
||||
+2
-1
@@ -42,13 +42,14 @@ 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.
|
||||
- The polish step treats pending fragments as the final release outcome, not prerelease history. If a feature is added and then renamed or fixed before the stable cut, ship the final feature bullet instead of separate prerelease-only breaking/fix entries.
|
||||
- GitHub release notes and prerelease notes use short top-level items with nested bullets for the change, user benefit, and any useful action note. The stable `CHANGELOG.md` can stay in compact single-line bullets.
|
||||
- `CHANGELOG.md`, GitHub release notes, and prerelease notes all use short top-level items with one nested bullet per distinct change, instead of packing a release's worth of detail into a single paragraph bullet. An item with only one thing to say stays inline on the top-level bullet. Release notes and prerelease notes additionally cover user benefit and any useful action note in their nested bullets.
|
||||
- `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`
|
||||
- from the second prerelease of a base version onward, the notes also open with a `## Changes since <previous tag>` section generated from the fragment diff against the previous beta/RC tag; keep fragment edits meaningful. Editorial-only rewording is filtered out of that section, while genuinely changed behavior and deleted fragments (reverted changes) are reported
|
||||
- existing prerelease notes are a reviewed baseline; later prerelease runs should replace stale beta/RC wording with the current outcome instead of appending fix churn
|
||||
- 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
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
type: added
|
||||
area: overlay
|
||||
|
||||
- Known-word subtitle highlights can now be colored by Anki card maturity (new, learning, young, mature) like asbplayer. Enable with `ankiConnect.knownWords.maturityEnabled`; the mature interval threshold (`matureThresholdDays`, default 21) and the four tier colors (`subtitleStyle.knownWordMaturityColors`) are configurable, and a runtime option toggles it in-session. The session help color legend shows the four tier colors while maturity highlighting is on.
|
||||
- Tiers follow Anki's own card counts: the interval tiers exclude cards in the learning/relearning queue, so a lapsed card shows the learning color instead of young (its interval is reset to at least 1 day, which previously made the learning tier unreachable). A note with a mature card alongside a relearning card still shows mature.
|
||||
@@ -1,5 +0,0 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- Applied configured primary POS exclusions consistently to merged trailing quote-particle tokens, preserved annotations for supplementary-plane kanji, and stopped treating katakana punctuation as kana-only annotation noise.
|
||||
- Kept kanji vocabulary tagged `名詞/非自立` eligible for N+1 highlighting, consistent with frequency, JLPT, and vocabulary persistence.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: docs
|
||||
area: launcher
|
||||
|
||||
- Documented the bundled-runtime launcher install, legacy launcher migration, package-managed updates, and the bundled Bun runtime's MIT and LGPL notices (installed under `/usr/share/licenses/subminer-bin` by the AUR package and included in `subminer-assets.tar.gz`).
|
||||
@@ -0,0 +1,4 @@
|
||||
type: changed
|
||||
area: launcher
|
||||
|
||||
- Every installed and downloadable launcher now uses the Bun runtime bundled with SubMiner instead of a system Bun. Recognized legacy launchers migrate automatically, Windows gets a `subminer.cmd` download, and first-run setup is reduced to a single optional launcher control with runtime repair guidance shown only when needed.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: internal
|
||||
area: ci
|
||||
|
||||
- Removed duplicate source and launcher smoke executions from the reusable quality gate while preserving every distinct test lane and failure artifact.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: internal
|
||||
area: verification
|
||||
|
||||
- Replaced mislabeled dist source reruns with a small Electron-runtime smoke check for compiled stats startup, HTTP service, native SQLite, port conflicts, and cleanup.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: changed
|
||||
area: sync
|
||||
|
||||
- Sync now uses compressed, incremental rsync transfers between compatible macOS and Linux machines, caching the last received snapshot per peer to cut traffic on later syncs. Machines without compatible rsync (including Windows) fall back to compressed scp, and older peers still work without the upload cache. Transfers abort after 30 minutes.
|
||||
@@ -1,4 +0,0 @@
|
||||
type: changed
|
||||
area: shortcuts
|
||||
|
||||
- Made the clipboard-video playlist shortcut configurable through `shortcuts.appendClipboardVideoToQueue`.
|
||||
@@ -1,8 +0,0 @@
|
||||
type: fixed
|
||||
area: anilist
|
||||
|
||||
- Season 2 and later files now resolve to the right AniList entry. AniList has no numbered seasons (sequels are separate entries titled `Zoku`, `Kan`, `2nd Season`), so the old `<title> Season N` search returned nothing and silently fell back to the season 1 entry: an Oregairu S03 episode loaded the season 1 character dictionary and pushed watch progress onto season 1. SubMiner now searches the base title and walks AniList `SEQUEL` relations to reach the requested season, ordering the franchise's TV entries by air date when the relation chain is incomplete.
|
||||
- When the season still cannot be located, SubMiner no longer writes progress to the season 1 entry; it skips the update and points at the manual AniList override instead. The character dictionary logs the same warning and states which entry it fell back to.
|
||||
- Character dictionary overrides are now scoped by detected season as well as directory, so seasons kept in one flat folder no longer share (or overwrite) each other's override, and an override now also pins the entry used for AniList watch progress: correcting a wrong match once fixes both the dictionary and progress tracking.
|
||||
- Cover art lookup uses the same season-aware resolution, so per-season art no longer falls back to the season 1 cover.
|
||||
- An unresolvable season is no longer cached as if it were a normal match, so the fallback cannot silently stick for every later episode of that series.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: anki
|
||||
|
||||
- Added `ankiConnect.fields.wordAudio` so word audio is read separately from the sentence-audio destination, fixing animated images that started moving immediately when `fields.audio` pointed to `SentenceAudio`.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: config
|
||||
|
||||
- Validate direct AnkiConnect, Kiku, and Senren settings before admitting them to runtime config, with warnings and defaults for invalid values.
|
||||
@@ -1,4 +0,0 @@
|
||||
type: fixed
|
||||
area: anki
|
||||
|
||||
- Prevented video startup from crashing when another process already owns the configured AnkiConnect proxy port, and added a notification explaining how to resolve the conflict.
|
||||
@@ -1,4 +0,0 @@
|
||||
type: fixed
|
||||
area: app
|
||||
|
||||
- Fixed "Service Crash" desktop notifications (KDE DrKonqi) after closing a video when running the Linux AppImage: the short-lived background bootstrap spawned a Chromium GPU child that outlived it (surviving `app.exit`) and died with SIGBUS at session end when the bootstrap's FUSE mount was finally released. The bootstrap now runs with the GPU in-process so it leaves no children behind, and the detached app's mount remains supervised until its Chromium children finish. Set `SUBMINER_NO_APPIMAGE_MOUNT_KEEPALIVE=1` to disable the detached-app mount supervisor.
|
||||
@@ -1,4 +0,0 @@
|
||||
type: fixed
|
||||
area: character dictionary
|
||||
|
||||
- Kept manual AniList overrides active across episodes in the same season directory when filename guesses differ.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: startup
|
||||
|
||||
- Fixed first launch exiting on macOS when the SubMiner config directory did not yet exist by creating it before acquiring the startup lock.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: jellyfin
|
||||
|
||||
- Jellyfin streams no longer leak URL-derived titles or credential-bearing stream URLs into metadata lookups, Anki source fields, Discord presence, stats identities, or AniList retry keys. Previously cached credential-bearing parser metadata is cleaned up without touching watch history or library assignments.
|
||||
@@ -0,0 +1,7 @@
|
||||
type: fixed
|
||||
area: jellyfin
|
||||
|
||||
- Jellyfin playback, subtitle, artwork, and remote-control URLs now authenticate with the `ApiKey` query parameter instead of the legacy `X-Emby-*` headers, so the integration works on Jellyfin 12 where legacy authorization is disabled by default.
|
||||
- "Play on SubMiner" keeps working on Jellyfin 12: the cast-target websocket answers keep-alive requests and reconnects when the server stops replying instead of silently dying after about a minute.
|
||||
- The Jellyfin "now playing" bar clears when you close or finish a cast video instead of running on to the end of the episode.
|
||||
- Anki cards mined from Jellyfin playback get the episode title in the misc info field again instead of "Unknown media".
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- Clicking a subtitle sidebar cue releases row focus, and Space no longer seeks back to a focused cue. Enter still seeks the focused cue, and Space keeps its configured playback action.
|
||||
@@ -1,4 +0,0 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- Fixed `mpv.pauseUntilOverlayReady` releasing playback seconds before tokenization warmup finished: startup subtitle priming emits the current cue untokenized so the overlay can paint early, and that emission was treated as the autoplay-readiness signal as soon as the overlay window loaded. The autoplay gate now ignores untokenized subtitle payloads while tokenization warmup is pending, so playback resumes only after the first tokenized delivery (or the post-warmup release). Most visible when resuming mid-episode or when a subtitle cue starts within the first two seconds.
|
||||
@@ -1,5 +0,0 @@
|
||||
type: fixed
|
||||
area: stats
|
||||
|
||||
- Stats reported 0 known words for every session after the known-word cache gained maturity tiers. The stats server carried its own copy of the cache parser that only recognized versions up to 3, so the new v4 file was read as "no cache" rather than as a format it should understand.
|
||||
- The cache format, its parser, and the derived known-word set now live in one module that both the cache manager and the stats server read, and the version dispatch ends in an exhaustive check so a future format bump fails the build instead of silently reporting zero. A cache that exists but does not parse now logs a warning rather than passing for an empty one.
|
||||
@@ -1,4 +0,0 @@
|
||||
type: fixed
|
||||
area: stats
|
||||
|
||||
- Relinking a title to a different AniList entry now updates its cover in the stats Library grid too. The detail view picked up the new art immediately while the grid kept the previous entry's image, so several unrelated titles could end up sharing one wrong cover. The library list is refetched after a relink, and cover responses now carry an ETag and revalidate instead of being cached for a day, so a stale image can no longer be served from the browser cache.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: stats
|
||||
|
||||
- Reject malformed resource IDs and partly invalid ID lists before stats library mutations or cover backfills run.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: stats
|
||||
|
||||
- Stats server port conflicts are reported through status notifications instead of crashing SubMiner. Startup and shutdown are also more robust: concurrent startup requests are shared, background stop no longer disconnects foreground dashboards, and shutdown bounds how long it waits for active requests.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: anki
|
||||
|
||||
- Closing the overlay while media timing review is still loading now cancels setup and modal retries, restores playback if the review paused it, and cleans up the hidden preview player.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: anki
|
||||
|
||||
- Media timing review now keeps playback paused while it is open when the dictionary popup or subtitle hover pause ends, as with Hachidori popups closing when the review appears. Playback resumes after the review closes unless a dictionary popup is still open.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: anki
|
||||
|
||||
- Treat `ankiConnect.media.maxMediaDuration: 0` as unlimited for stats dashboard mining, matching overlay mining and configuration.
|
||||
@@ -1,7 +0,0 @@
|
||||
type: internal
|
||||
area: tokenizer
|
||||
|
||||
- Added a golden-file regression corpus for the tokenizer/annotation pipeline: recorded Yomitan backend responses and MeCab tokens replay through the real tokenizeSubtitle pipeline in bun tests without Electron or dictionaries.
|
||||
- Added `record-tokenizer-fixture:electron` script to capture new fixtures from a live Yomitan/MeCab session, with flags for known words, JLPT levels, and annotation toggles.
|
||||
- Seeded eleven fixtures covering the #147–#156 regression classes (grammar-helper suppression, lexical くれる, kanji non-independent nouns, N+1 targeting, reading collisions, unparsed runs, ordinal/honorific prefixes).
|
||||
- Added `compare-yomitan-api:electron` script that diffs SubMiner tokenization against a stock Yomitan instance via the yomitan-api bridge (segmentation, readings, headword forms).
|
||||
@@ -0,0 +1,14 @@
|
||||
type: added
|
||||
area: dictionary
|
||||
|
||||
- Added a bundled Hachidori dictionary backend alongside the default Yomitan backend. Select it with `dictionaryBackend` and restart SubMiner.
|
||||
- The tray and dictionary-settings shortcut follow the selected backend. `--hachidori` opens Hachidori settings, while `--yomitan` opens Yomitan settings unless a read-only external Yomitan profile is configured. This restriction also applies with Hachidori active, without blocking Hachidori settings.
|
||||
- Hachidori integrates with subtitle scanning, popup controls, lookup tracking, character dictionaries, and Anki media enrichment, with separate dictionaries and settings for each backend. Linked Docker hosts receive character dictionary uploads through `hachidori.externalHostManagementUrl`, retry busy imports, and replace the previous dictionary only after a successful import.
|
||||
- Hachidori auto-populates its first Anki template from SubMiner's deck, tags, and field mappings, detects an unambiguous matching note type, and preserves existing custom templates apart from the deck, which follows `ankiConnect.deck` so polling mode enriches Hachidori cards. Anki discovery retries after an unavailable connection.
|
||||
- Hachidori saves downloadable word audio before sending a note through SubMiner's Anki proxy, so freshly mined animated cards include the word-audio delay.
|
||||
- Both bundled dictionary backends reload their background code on startup so extension updates take effect while preserving installed dictionaries and settings. Failed Yomitan connection-setting updates can be retried without manually resetting the managed Anki endpoint.
|
||||
- First-run setup remembers each backend that finished it, including when reopened for legacy plugin cleanup, so switching back does not repeat setup, and the launcher gates playback on the backend the running app started with. Current incomplete or cancelled setup takes precedence over stale completion history. A running Yomitan session keeps using its external profile until it restarts into Hachidori.
|
||||
- Stats dashboard mining and deck lookup use the selected backend, with the Anki proxy enabled or disabled. Dashboard cards retain the selected history line and media instead of being processed again with the current mpv subtitle, with both Yomitan and Hachidori. Stats cards carry a `SubMiner::Stats` tag so polling also preserves their context. Hachidori word mining supplies dictionary aliases, IDs, and frequency metadata to its native Anki renderer. Settings labels for popup pause and the dictionary deck no longer name Yomitan, and the backend selector sits with the other dictionary settings.
|
||||
- Hachidori scans, dictionary counts, and settings reads wait for the dictionary engine to finish loading or importing instead of caching empty results.
|
||||
- First-run setup can link an external Hachidori dictionary host in an app, browser, or Docker container, verify its library, and unlink back to local dictionaries. The optional host controls are collapsed by default and explain which apps or containers must stay running. Unresponsive connection checks time out so setup remains usable. Anki mining, media enrichment, and custom toolbar buttons stay in SubMiner, including when editing buttons while linked.
|
||||
- Hachidori frequency highlighting reuses dictionary-entry ranks and fills missing ranks through its existing API. Entries without a matching definition may remain unranked. The bundled integration is maintained in a pinned fork submodule using upstream HoshiDicts and WASM binaries.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: docs
|
||||
area: dictionary
|
||||
|
||||
- Documented backend selection, separate Hachidori dictionary and Anki setup, the settings flags and shortcuts, and external host linking with local Anki mining and the running requirements for each host type.
|
||||
@@ -1,5 +0,0 @@
|
||||
type: added
|
||||
area: launcher
|
||||
|
||||
- After a watch-history episode ends or mpv closes, the fzf or rofi launcher returns to that series with options to play the previous episode, rewatch, play the next episode, select another episode, or quit SubMiner. Previous and Next continue across season directories.
|
||||
- The action menu shown right after picking a series from `subminer -H` now also offers the previous episode, matching the menu shown after playback.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- Keep Hyprland recovery dialogs above SubMiner windows so overlay placement updates do not cover their Wait and Close buttons.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: docs
|
||||
area: subtitles
|
||||
|
||||
- Documented subtitle generation: model choice, VAD behavior, reference-track guided splitting, fallback behavior, and known limits.
|
||||
@@ -0,0 +1,7 @@
|
||||
type: added
|
||||
area: subtitles
|
||||
|
||||
- Generate Japanese SRT subtitles locally with whisper.cpp from a modal (Ctrl+Shift+G), the empty subtitle sidebar's generation button, or `subminer generate-subs`, with progress, cancellation, and automatic loading into mpv.
|
||||
- Pick an official multilingual model (including quantized variants) with size and accuracy guidance and download it in-app, or point Settings at an existing model. `large-v3-turbo` is recommended when CUDA support is detected, `small` otherwise. whisper-cli, ffmpeg, and ffprobe are found on PATH unless overridden, and missing tools are named before any download starts.
|
||||
- Optional "Focus on spoken dialogue" mode uses a separately downloadable Silero VAD model, keeping uncertain audible sections so dialogue under music is not dropped (songs may be transcribed too).
|
||||
- Long passages are split near detected speech starts or quiet pauses, guided by an eligible embedded or external subtitle track already loaded in mpv when one is available, to reduce early subtitle timing. Each passage runs in a fresh Whisper process to avoid repeated-character output.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: docs
|
||||
area: jellyfin
|
||||
|
||||
- Clarify that Windows mpv playback and Jellyfin casting can use a configured executable path instead of PATH.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: jellyfin
|
||||
|
||||
- Honor the configured mpv executable when Jellyfin starts playback, allowing casting when mpv is installed outside PATH, and detect portable plugins beside the selected executable.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: docs
|
||||
area: jellyfin
|
||||
|
||||
- Documented how Jellyfin media titles and stats identities keep stream credentials out of metadata.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: added
|
||||
area: jimaku
|
||||
|
||||
- Jimaku modal gains Anime / Live action tabs so searches can pull from Jimaku's live action catalogue instead of only anime entries (Arrow Left / Right switch tabs).
|
||||
@@ -0,0 +1,4 @@
|
||||
type: docs
|
||||
area: mining
|
||||
|
||||
- Document independent screenshot selection in media timing review.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: added
|
||||
area: mining
|
||||
|
||||
- Choose a still screenshot independently of the audio in media timing review, with a live preview, a separate time slider, and decoded-frame stepping. Local video and seekable remote streams such as Jellyfin are supported.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: added
|
||||
area: overlay
|
||||
|
||||
- The overlay picks up non-conflicting keyboard bindings from mpv defaults, `input.conf`, and loaded scripts. SubMiner controls and explicitly disabled bindings take precedence; discovered bindings are session-only and not listed in the help menu.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- Overlay modals such as the media timing review open on the first attempt when their dedicated window is created on demand, instead of after a four-second retry.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: changed
|
||||
area: release
|
||||
|
||||
- Reduced installer and unpacked app size by dropping demo media, source maps, TypeScript sources, test fixtures, and unused Koffi binaries from the package, and sharing one Japanese UI font across windows. Release builds now publish package size reports with comparisons against the previous release.
|
||||
@@ -1,4 +0,0 @@
|
||||
type: internal
|
||||
area: overlay
|
||||
|
||||
- Consolidated renderer modal state handling into a descriptor registry.
|
||||
@@ -1,4 +0,0 @@
|
||||
type: internal
|
||||
area: release
|
||||
|
||||
- Consolidated pull request, stable release, and prerelease quality checks in one reusable workflow, with Lua mpv plugin tests and blocking high-severity dependency audits running in every gate.
|
||||
@@ -1,4 +0,0 @@
|
||||
type: fixed
|
||||
area: launcher
|
||||
|
||||
- Rofi menu prompts now keep a space between the prompt text and the input field instead of running into the search placeholder.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: overlay
|
||||
|
||||
- Cancel pending Linux overlay window replacements during teardown so a delayed close callback cannot reopen the overlay.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: anki
|
||||
|
||||
- Keep word-card sentence furigana in sync with full stats-search context and expanded timing-review selections. Clear stale readings if regeneration fails.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: docs
|
||||
area: config
|
||||
|
||||
- Clarified live-setting save feedback, mixed restart warnings, and subtitle-generation reload behavior.
|
||||
@@ -0,0 +1,5 @@
|
||||
type: fixed
|
||||
area: config
|
||||
|
||||
- Settings marked LIVE now use the same reload policy as save results, fixing false restart warnings for notifications and subtitle generation.
|
||||
- Mixed saves apply live changes and list only sections with changed fields that require a restart.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: added
|
||||
area: overlay
|
||||
|
||||
- Select dialogue across subtitle sidebar rows and copy it without timestamps using Ctrl/Cmd+C or the Copy button. Selecting does not seek or require mining a card.
|
||||
@@ -1,5 +0,0 @@
|
||||
type: fixed
|
||||
area: stats
|
||||
|
||||
- Validated nested and legacy AnkiConnect settings after splitting the resolver, preserving valid modern overrides while warning and falling back for invalid primitive values.
|
||||
- Hardened stats routes against malformed IDs and static paths, stalled AniList searches, word-mining media collisions, missing Yomitan bridges, and throwing timing observers.
|
||||
@@ -1,5 +0,0 @@
|
||||
type: added
|
||||
area: stats
|
||||
|
||||
- Added a "Delete Entry" action to the stats Library detail view. It removes the whole title in one step — every episode, session, subtitle line, rollup, cover art and vocabulary counts derived from them — and the title disappears from the Library grid. Previously only individual episodes and sessions could be deleted, so a mistakenly created entry had to be cleared episode by episode and still lingered in the library.
|
||||
- Deletion progress is now shown app-wide instead of only on Overview and Sessions. Every delete (session, session group, episode, and library entry) drives a sweeping bar at the top of the window plus a bottom-right status toast, and both stay visible across tab switches, detail views, and the in-app stats overlay window. The per-tab indicators previously went blank the moment you left the tab that started the delete.
|
||||
@@ -1,7 +0,0 @@
|
||||
type: fixed
|
||||
area: stats
|
||||
|
||||
- Made deleting stats data much faster and stopped it stalling playback. Every delete used to recompute each affected word's totals by re-reading that word's whole occurrence history across the library, which meant one random read into the largest table per occurrence. On a 960-session library that cost 3.8s to delete a single session and over a minute to clear a 12-episode title, and because the stats server runs in the app process when the app owns it, that time was spent blocking mpv.
|
||||
- Word and kanji occurrences now store the subtitle line's timestamp alongside the count, so those totals are answered from a covering index instead of the subtitle-line table, and a delete only subtracts what it actually removed rather than recomputing untouched entries. Measured on the same 960-session library: deleting one session 3832ms to 190ms, a ten-session day group 6453ms to 433ms, one episode 5437ms to 263ms, and a whole 12-episode title 60556ms to 621ms.
|
||||
- Opening the Vocabulary tab no longer stalls either. It was computing "seen in N titles" for every word in the library before ordering by frequency and keeping the top 100, so the whole occurrence history was walked to produce one page. It now picks the page first and counts only those rows: 2136ms to 62ms on the same library.
|
||||
- The first launch after upgrading migrates the existing stats database in place (about 3s for a 288k-line library) and it grows roughly 20% from the added index. Vocabulary dates recorded before the upgrade keep working while the migration runs.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: changed
|
||||
area: stats
|
||||
|
||||
- The stats server now rejects requests from non-loopback hosts and browser origins and requires `application/json` for mutation bodies. The in-app stats overlay loads from the local server so it shares the same protection. Reverse-proxied or Tailscale Serve dashboards are unsupported; scripts that POST must set a JSON content type.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: docs
|
||||
area: config
|
||||
|
||||
- Documented the subtitle selector setting, shortcut override, and primary/secondary track controls.
|
||||
@@ -0,0 +1,5 @@
|
||||
type: added
|
||||
area: overlay
|
||||
|
||||
- Added an optional Catppuccin subtitle selection modal for primary and secondary mpv tracks. Enable it in Settings under Behavior, then press g followed by s. Disabling it restores mpv's subtitle selection binding.
|
||||
- Single-key actions take priority over configured sequence prefixes. Conflicting sequences are disabled with a warning, and the existing y commands stay reserved.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: fixed
|
||||
area: subtitles
|
||||
|
||||
- Keep the subtitle sidebar near playback during gaps when the subtitle file has a cue starting at zero.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: docs
|
||||
area: sync
|
||||
|
||||
- Documented compressed transfers, incremental sync cache storage, and compatibility with older peers.
|
||||
@@ -1,8 +0,0 @@
|
||||
type: added
|
||||
area: sync
|
||||
|
||||
- Added cross-machine immersion sync for stats and watch history over SSH, available as a window (**Sync Stats & History** in the tray menu, or `subminer sync --ui`) and as a command (`subminer sync <host>`, with `--push` / `--pull` for one-way insert-only transfers). The window keeps saved devices with per-host direction, one-click sync with live stage-by-stage progress and separate merge summaries for each machine, connection testing for first-time setup, cancellable runs while the app/stats server/playback is active, and manual database snapshots (create/merge/reveal/delete, stored in `/tmp/subminer-db-snapshots/` by default). Hosts with auto-sync enabled sync in the background on a configurable interval, including during playback, with results reported as overlay notifications; hosts synced from the CLI are remembered in `<config dir>/sync-hosts.json` and appear in the window automatically.
|
||||
- Merges are an insert-only union keyed on session UUIDs / video keys / series title keys, so re-syncing is idempotent and nothing is double-counted: each side snapshots its database (`VACUUM INTO`) from a consistent WAL point, snapshots are exchanged with `scp`, and each machine merges the other's data transactionally. Lifetime totals and daily/monthly rollups are updated incrementally (history older than the session retention window is preserved), unfinished sessions are excluded until a later sync sees them finalized, and remote-only historical rollups are copied only when they do not conflict with retained local session history. Sync aborts on stats schema version mismatches and refuses to run while the stats daemon or a live mpv session is active (`--force` overrides).
|
||||
- The sync engine runs only inside the app: the sync window and the `subminer sync` command both delegate to `SubMiner --sync-cli` (headless, works over SSH with no display), so neither machine needs bun or the command-line launcher. A remote machine only needs SubMiner itself, found automatically as the app binary or via the launcher proxy.
|
||||
- Windows remotes are supported: sync detects the remote shell (POSIX, cmd, or PowerShell) and manages remote temp files through SubMiner itself (`sync --make-temp` / `--remove-temp`) instead of `mktemp` / `rm`, so a Windows machine with the built-in OpenSSH Server works as a sync remote, found in its default Windows install location automatically.
|
||||
- Added supporting flags: `subminer sync <host> --check` tests the SSH connection and remote launcher availability without syncing, `subminer sync --snapshot <file>` and `--merge <file>` expose the underlying steps for manual transfers, and `subminer sync --json` emits machine-readable NDJSON progress (the protocol the sync window consumes).
|
||||
@@ -0,0 +1,4 @@
|
||||
type: docs
|
||||
area: stats
|
||||
|
||||
- Documented TMDB linking, provider reassignment, merge compatibility, and credential command caching.
|
||||
@@ -0,0 +1,6 @@
|
||||
type: added
|
||||
area: stats
|
||||
|
||||
- Live-action dramas and movies in the stats Library get posters, synopses, and titles from TMDB. Release builds include a project key; `tmdb.apiKey` (or `tmdb.apiKeyCommand`) overrides it and is required when running from source.
|
||||
- Titles AniList cannot match are looked up on TMDB automatically when the parsed filename matches a Japanese live-action title exactly; otherwise use the new **Link to TMDB** action. Entries linked to the same TMDB title merge into one card, and the Library kind selector gained a Live Action option.
|
||||
- Provider reassignment keeps the previous link and artwork if the replacement download fails. Merges and sync keep AniList and TMDB identities separate, and the merge dialog explains mixed selections instead of failing.
|
||||
@@ -1,4 +0,0 @@
|
||||
type: added
|
||||
area: overlay
|
||||
|
||||
- Added TsukiHime subtitle downloads for the current video, with Japanese primary and configured secondary-language tracks loaded directly into mpv.
|
||||
@@ -1,4 +0,0 @@
|
||||
type: internal
|
||||
area: stats
|
||||
|
||||
- Removed the unused stats IPC data transport and unified the stats dashboard's HTTP wire types with the backend contract.
|
||||
@@ -1,7 +0,0 @@
|
||||
type: internal
|
||||
area: tokenizer
|
||||
|
||||
- Added `verify-known-word-highlights:electron` script: tokenizes a real subtitle file through the app's Yomitan/MeCab pipeline with the live known-word cache, prints each line in the configured tier colors, and summarizes the tier counts so highlighting can be checked outside of playback.
|
||||
- Added `--audit`, which re-derives every highlighted tier from live Anki card data (`notesInfo` + `cardsInfo` intervals) and reports each token whose rendered tier disagrees, catching both stale cache entries and tier-classification bugs.
|
||||
- Added `--profile-copy` so the check can run while SubMiner is open (Electron locks the Yomitan userData dir), plus `--refresh`, `--limit`, `--json`, and `--quiet`.
|
||||
- Added `KnownWordCacheManager.getKnownWordMatchNoteIds`, exposing the note ids behind a known-word match so an audit can trace a rendered tier back to the exact Anki notes.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: docs
|
||||
area: anki
|
||||
|
||||
- Documented the separate word-audio field mapping; existing animated images need regeneration to pick up the fix.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: changed
|
||||
area: yomitan
|
||||
|
||||
- Updated bundled Yomitan with upstream 26.9.8 changes, including historical Japanese kana transformations, Ukrainian language support, and improvements to Anki duplicate searches and audio retrieval.
|
||||
@@ -0,0 +1,4 @@
|
||||
type: docs
|
||||
area: stats
|
||||
|
||||
- Document YouTube channel filtering and video statistics in the Library.
|
||||
@@ -0,0 +1,5 @@
|
||||
type: added
|
||||
area: stats
|
||||
|
||||
- YouTube channels are now a separate Library media kind. Existing channel entries migrate automatically without changing viewing history or manual video assignments.
|
||||
- Added All Titles, Anime, and YouTube Library filters. Channels stay out of AniList matching, season repair, and duplicate recommendations, and merges or video moves cannot mix an anime entry with a YouTube channel.
|
||||
+67
-3
@@ -6,6 +6,48 @@
|
||||
*/
|
||||
{
|
||||
|
||||
// ==========================================
|
||||
// Dictionary Backend
|
||||
// Select the dictionary lookup backend: yomitan or hachidori.
|
||||
// Restart SubMiner after changing the backend. Each backend keeps separate settings and dictionaries.
|
||||
// ==========================================
|
||||
"dictionaryBackend": "yomitan", // Dictionary lookup backend. Restart SubMiner after changing this setting. Values: yomitan | hachidori
|
||||
|
||||
// ==========================================
|
||||
// Hachidori External Dictionary Imports
|
||||
// Configure the linked Docker host management URL, for example http://127.0.0.1:8780.
|
||||
// Used only while Hachidori is linked to an external host.
|
||||
// ==========================================
|
||||
"hachidori": {
|
||||
"externalHostManagementUrl": "" // Docker host management URL for automatic character dictionary uploads and replacement. Empty disables external uploads.
|
||||
}, // Configure the linked Docker host management URL, for example http://127.0.0.1:8780.
|
||||
|
||||
// ==========================================
|
||||
// Subtitle Selection
|
||||
// Select primary and secondary mpv subtitle tracks from the overlay.
|
||||
// Hot-reload: enabling or disabling updates the session shortcut immediately.
|
||||
// ==========================================
|
||||
"subtitleSelection": {
|
||||
"enabled": false // Use the SubMiner modal to select primary and secondary subtitle tracks. When enabled, its shortcut overrides mpv subtitle selection. Values: true | false
|
||||
}, // Select primary and secondary mpv subtitle tracks from the overlay.
|
||||
|
||||
// ==========================================
|
||||
// Japanese Subtitle Generation
|
||||
// Generate timed Japanese subtitles from local audio using whisper.cpp.
|
||||
// Configure an existing GGML model path or explicitly download a SubMiner-managed model.
|
||||
// Hot-reload: settings apply to the next generation or model download.
|
||||
// ==========================================
|
||||
"subtitleGeneration": {
|
||||
"whisperPath": "", // Optional path override for whisper.cpp. Leave empty to find whisper-cli on PATH.
|
||||
"modelPath": "", // Path to an existing multilingual whisper.cpp GGML model. Leave empty to use a SubMiner-managed model. A configured path always takes precedence.
|
||||
"managedModel": "small", // Multilingual whisper.cpp model to use when modelPath is empty. Download it explicitly from the generation modal or launcher. Values: tiny | tiny-q5_1 | tiny-q8_0 | base | base-q5_1 | base-q8_0 | small | small-q5_1 | small-q8_0 | medium | medium-q5_0 | medium-q8_0 | large-v1 | large-v2 | large-v2-q5_0 | large-v2-q8_0 | large-v3 | large-v3-q5_0 | large-v3-turbo | large-v3-turbo-q5_0 | large-v3-turbo-q8_0
|
||||
"threads": 4, // Positive integer CPU thread count for whisper.cpp Japanese transcription.
|
||||
"ffmpegPath": "", // Optional FFmpeg path override for audio extraction. Leave empty to find ffmpeg on PATH.
|
||||
"ffprobePath": "", // Optional FFprobe path override for audio tracks and timing. Leave empty to find ffprobe on PATH.
|
||||
"vadModelPath": "", // Path to a whisper.cpp Silero VAD model. Enables dialogue-focused generation while retaining uncertain audible sections, which may include songs. Leave empty to transcribe the full audio.
|
||||
"vadPath": "" // Optional speech detector executable override. With vadModelPath configured, leave empty to find whisper-vad-speech-segments or vad-speech-segments on PATH.
|
||||
}, // Generate timed Japanese subtitles from local audio using whisper.cpp.
|
||||
|
||||
// ==========================================
|
||||
// Visible Overlay Auto-Start
|
||||
// Show the visible subtitle overlay automatically after managed mpv playback starts SubMiner.
|
||||
@@ -206,6 +248,8 @@
|
||||
"openRuntimeOptions": "CommandOrControl+Shift+O", // Accelerator that opens the runtime options modal.
|
||||
"openJimaku": "Ctrl+Shift+J", // Accelerator that opens the Jimaku subtitle search modal.
|
||||
"openTsukihime": "Ctrl+Shift+T", // Accelerator that opens the TsukiHime subtitle search modal (configured secondary/Japanese primary tabs).
|
||||
"openSubtitleSelection": "g-s", // Open subtitle selection when enabled. Use g-s to press g then s. Set null to unbind.
|
||||
"openSubtitleGeneration": "Ctrl+Shift+G", // Accelerator that opens the standalone Japanese subtitle generation 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.
|
||||
@@ -523,7 +567,7 @@
|
||||
// ==========================================
|
||||
// AnkiConnect Integration
|
||||
// Automatic Anki updates and media generation options.
|
||||
// Hot-reload: ankiConnect.ai.enabled, media.normalizeAudio/mirrorMpvVolume, knownWords, nPlusOne, fields.word/audio/image/sentence/miscInfo, behavior.autoUpdateNewCards, isLapis.sentenceCardModel, and isKiku.fieldGrouping update live while SubMiner is running.
|
||||
// Hot-reload: ankiConnect.ai.enabled, media.normalizeAudio/mirrorMpvVolume/reviewTiming, knownWords, nPlusOne, fields.word/audio/wordAudio/image/sentence/miscInfo, behavior.autoUpdateNewCards, isLapis.sentenceCardModel, isKiku.fieldGrouping, isSenren.fieldGrouping, and lapisKiku.wordCardKind update 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.
|
||||
// ==========================================
|
||||
@@ -544,6 +588,7 @@
|
||||
"fields": {
|
||||
"word": "Expression", // Card field for the mined word or expression text.
|
||||
"audio": "ExpressionAudio", // Card field that receives generated sentence audio.
|
||||
"wordAudio": "ExpressionAudio", // Existing word-audio field read to time the frozen first frame of animated images. This mapping is only used for synchronization.
|
||||
"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).
|
||||
@@ -569,9 +614,10 @@
|
||||
"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
|
||||
"normalizeAudio": true, // Normalize generated sentence audio loudness during media extraction. Changes apply live. Values: true | false
|
||||
"mirrorMpvVolume": true, // Apply mpv's current software volume curve to generated sentence audio. Changes apply live. Values: true | false
|
||||
"reviewTiming": false, // Review and preview subtitle media timing before SubMiner creates or enriches a mined card. Values: true | false
|
||||
"audioPadding": 0, // Seconds of padding appended to both ends of generated sentence audio and animated AVIF clips.
|
||||
"fallbackDuration": 3, // Fallback clip duration in seconds when subtitle timing data is unavailable.
|
||||
"maxMediaDuration": 30 // Maximum allowed media clip duration in seconds.
|
||||
"maxMediaDuration": 30 // Maximum allowed media clip duration in seconds. 0 disables the cap.
|
||||
}, // Media setting.
|
||||
"knownWords": {
|
||||
"highlightEnabled": false, // Enable fast local highlighting for words already known in Anki. Values: true | false
|
||||
@@ -605,7 +651,15 @@
|
||||
"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 // When Kiku field grouping is "auto", delete the duplicate source card after grouping completes. Values: true | false
|
||||
} // Is kiku setting.
|
||||
}, // Is kiku setting.
|
||||
"isSenren": {
|
||||
"enabled": false, // Enable Senren-specific duplicate handling (scene-switching field grouping, including miscInfo grouping). Mutually exclusive with isKiku.enabled. Values: true | false
|
||||
"fieldGrouping": "auto", // Senren duplicate-card field grouping mode (scene switching). Values: auto | manual | disabled
|
||||
"deleteDuplicateInAuto": true // When Senren field grouping is "auto", delete the duplicate source card after grouping completes. Values: true | false
|
||||
}, // Is senren setting.
|
||||
"lapisKiku": {
|
||||
"wordCardKind": "word-and-sentence" // Card-type flag SubMiner marks on Kiku/Lapis word cards. Only one flag is set at a time; the others are cleared. Requires isKiku.enabled or isLapis.enabled. Values: word-and-sentence | click | sentence | audio | none
|
||||
} // Lapis kiku setting.
|
||||
}, // Automatic Anki updates and media generation options.
|
||||
|
||||
// ==========================================
|
||||
@@ -631,6 +685,16 @@
|
||||
"maxSearchResults": 10 // Maximum TsukiHime search results returned.
|
||||
}, // TsukiHime subtitle search configuration for Japanese primary and configured secondary subtitles. No API key required.
|
||||
|
||||
// ==========================================
|
||||
// TMDB
|
||||
// TMDB (The Movie Database) metadata for live-action dramas and movies in the stats Library: posters, synopses, and grouping by show.
|
||||
// Hot-reload: TMDB changes apply to the next TMDB request.
|
||||
// ==========================================
|
||||
"tmdb": {
|
||||
"apiKey": "", // Your own TMDB API key or read access token for live-action posters and synopses in the stats Library. Release builds bundle a project key, so set this only to use your own quota or when running from source (free under Settings > API on themoviedb.org).
|
||||
"apiKeyCommand": "" // Shell command that prints the TMDB API key to stdout. Used instead of apiKey to avoid storing the key in plain text.
|
||||
}, // TMDB (The Movie Database) metadata for live-action dramas and movies in the stats Library: posters, synopses, and grouping by show.
|
||||
|
||||
// ==========================================
|
||||
// YouTube Playback Settings
|
||||
// Defaults for managed subtitle language preferences and YouTube subtitle loading.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { existsSync, readFileSync, statSync } from 'node:fs';
|
||||
import { extname, join, posix, resolve, sep } from 'node:path';
|
||||
import type { DefaultTheme, HeadConfig, TransformContext, UserConfig } from 'vitepress';
|
||||
@@ -26,6 +27,9 @@ function optionalEnv(value: string | undefined): string | undefined {
|
||||
const base = normalizeBase(optionalEnv(process.env.SUBMINER_DOCS_BASE) ?? '/');
|
||||
const outDir = optionalEnv(process.env.SUBMINER_DOCS_OUT_DIR);
|
||||
const docsSourceDir = optionalEnv(process.env.SUBMINER_DOCS_SOURCE_DIR) ?? process.cwd();
|
||||
// The tracked `docs-site/` checkout, which stays a git working tree even when
|
||||
// `docsSourceDir` points at an untracked release snapshot. Used for git lookups only.
|
||||
const repoDocsDir = optionalEnv(process.env.SUBMINER_DOCS_REPO_DIR) ?? process.cwd();
|
||||
const channel = normalizeChannel(optionalEnv(process.env.SUBMINER_DOCS_CHANNEL));
|
||||
const docsVersion = optionalEnv(process.env.SUBMINER_DOCS_VERSION);
|
||||
const latestStable = optionalEnv(process.env.SUBMINER_DOCS_LATEST_STABLE) ?? 'v0.18.0';
|
||||
@@ -82,15 +86,18 @@ function pageToRoute(page: string): string | null {
|
||||
return route ? `/${route}` : '/';
|
||||
}
|
||||
|
||||
// Only the root channel is indexable. `main` and every /v/<version>/ archive are
|
||||
// near-verbatim copies of it, so they own their URL via a self-referential canonical
|
||||
// and are excluded from the index instead of being consolidated onto root. Uniform
|
||||
// self-canonical plus noindex avoids mixing noindex with a cross-page canonical,
|
||||
// which Google treats as a conflicting signal.
|
||||
const isIndexableChannel = channel === 'stable-root';
|
||||
|
||||
function pageToCanonicalHref(page: string): string | null {
|
||||
const route = pageToRoute(page);
|
||||
if (!route) return null;
|
||||
|
||||
if (channel === 'main') {
|
||||
return `${DOCS_HOSTNAME}${canonicalRouteWithBase(route)}`;
|
||||
}
|
||||
|
||||
if (channel === 'stable-archive' && docsVersion !== latestStable) {
|
||||
if (!isIndexableChannel) {
|
||||
return `${DOCS_HOSTNAME}${canonicalRouteWithBase(route)}`;
|
||||
}
|
||||
|
||||
@@ -106,7 +113,9 @@ function transformPageHead({ page }: TransformContext): HeadConfig[] {
|
||||
const href = pageToCanonicalHref(page);
|
||||
const head: HeadConfig[] = href ? [['link', { rel: 'canonical', href }]] : [];
|
||||
|
||||
if (channel === 'main') {
|
||||
// Crawlable so links still pass through, but out of the index: ~30 archived copies
|
||||
// of every page otherwise soak up the crawl budget the current docs need.
|
||||
if (!isIndexableChannel) {
|
||||
head.push(['meta', { name: 'robots', content: 'noindex,follow' }]);
|
||||
}
|
||||
|
||||
@@ -287,6 +296,39 @@ const versionItems = [
|
||||
})),
|
||||
];
|
||||
|
||||
function sitemapUrlToPage(url: string): string {
|
||||
const route = url.replace(/\.html$/, '').replace(/^\/+|\/+$/g, '');
|
||||
return route ? `${route}.md` : 'index.md';
|
||||
}
|
||||
|
||||
// VitePress derives <lastmod> by running `git log` inside its source dir. Production
|
||||
// builds point that at an untracked snapshot of the release tag, so the lookup comes
|
||||
// back empty and the sitemap ships with no dates at all. Resolve it from the tracked
|
||||
// checkout at the ref being built instead.
|
||||
function lastModifiedFor(url: string): string | undefined {
|
||||
const ref = docsVersion && docsVersion !== 'main' ? docsVersion : 'HEAD';
|
||||
const result = spawnSync('git', ['log', '-1', '--format=%cI', ref, '--', sitemapUrlToPage(url)], {
|
||||
cwd: repoDocsDir,
|
||||
encoding: 'utf8',
|
||||
});
|
||||
|
||||
return (result.status === 0 && result.stdout.trim()) || undefined;
|
||||
}
|
||||
|
||||
// Only the root channel publishes a sitemap. Archived and `main` builds would emit
|
||||
// their own copies listing the same canonical URLs, which just advertises the
|
||||
// duplicate trees we are trying to keep out of the index.
|
||||
const sitemap: UserConfig['sitemap'] = isIndexableChannel
|
||||
? {
|
||||
hostname: DOCS_HOSTNAME,
|
||||
transformItems(items) {
|
||||
return items
|
||||
.filter((item) => item.url !== 'README' && item.url !== `${DOCS_HOSTNAME}/README`)
|
||||
.map((item) => ({ ...item, lastmod: item.lastmod ?? lastModifiedFor(item.url) }));
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const nav: DefaultTheme.NavItem[] = [
|
||||
{ text: 'Home', link: '/' },
|
||||
{ text: 'Get Started', link: '/installation' },
|
||||
@@ -306,7 +348,6 @@ const sidebar: DefaultTheme.SidebarItem[] = [
|
||||
{ text: 'Usage', link: '/usage' },
|
||||
{ text: 'Mining Workflow', link: '/mining-workflow' },
|
||||
{ text: 'Launcher Script', link: '/launcher-script' },
|
||||
{ text: 'Feature Demos', link: '/demos' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -328,6 +369,7 @@ const sidebar: DefaultTheme.SidebarItem[] = [
|
||||
{ text: 'Jellyfin', link: '/jellyfin-integration' },
|
||||
{ text: 'YouTube', link: '/youtube-integration' },
|
||||
{ text: 'Jimaku', link: '/jimaku-integration' },
|
||||
{ text: 'Subtitle Generation', link: '/subtitle-generation' },
|
||||
{ text: 'TsukiHime', link: '/tsukihime-integration' },
|
||||
{ text: 'AniList', link: '/anilist-integration' },
|
||||
{ text: 'AniSkip', link: '/aniskip-integration' },
|
||||
@@ -420,14 +462,7 @@ const config: UserConfig = {
|
||||
appearance: 'dark',
|
||||
cleanUrls: true,
|
||||
metaChunk: true,
|
||||
sitemap: {
|
||||
hostname: DOCS_HOSTNAME,
|
||||
transformItems(items) {
|
||||
return items.filter(
|
||||
(item) => item.url !== 'README' && item.url !== `${DOCS_HOSTNAME}/README`,
|
||||
);
|
||||
},
|
||||
},
|
||||
sitemap,
|
||||
transformHead: transformPageHead,
|
||||
lastUpdated: true,
|
||||
srcExclude: ['subagents/**', 'README.md'],
|
||||
|
||||
+4
-2
@@ -1,4 +1,4 @@
|
||||
# SubMiner Docs
|
||||
# SubMiner docs
|
||||
|
||||
In-repo VitePress documentation source for SubMiner.
|
||||
|
||||
@@ -38,8 +38,10 @@ bun run docs:dev
|
||||
The public docs root is stable-only:
|
||||
|
||||
- `/` serves the latest stable release docs.
|
||||
- `/main/` serves development docs from `main` and is marked `noindex,follow`.
|
||||
- `/main/` serves development docs from `main`.
|
||||
- `/v/<version>/` serves stable release archives.
|
||||
- Prerelease tags do not update the docs site.
|
||||
|
||||
Only `/` is indexable. `/main/` and every `/v/<version>/` page carries a self-referential canonical plus `noindex,follow`, and the generated `_headers` file repeats that as an `X-Robots-Tag`. They stay crawlable so their links still resolve, but ~30 archived copies of every page would otherwise consume the crawl budget the current docs need. Only the root build emits `sitemap.xml`, and its `<lastmod>` dates come from `git log` against the tracked checkout at the released tag, because the build renders from an untracked snapshot that VitePress cannot date itself.
|
||||
|
||||
Keep Cloudflare Git auto-deploy disabled. The production deploy is `.github/workflows/docs-pages.yml`, which uploads `.tmp/docs-versioned-site` with `--branch main` so tag-triggered runs update Production instead of creating preview deployments.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user