diff --git a/assets/minecard.mp4 b/assets/minecard.mp4 index 3f9a759..1865a6d 100644 Binary files a/assets/minecard.mp4 and b/assets/minecard.mp4 differ diff --git a/assets/minecard.webm b/assets/minecard.webm index b98e3be..eaf7e42 100644 Binary files a/assets/minecard.webm and b/assets/minecard.webm differ diff --git a/assets/minecard.webp b/assets/minecard.webp index 4d100f3..9740063 100644 Binary files a/assets/minecard.webp and b/assets/minecard.webp differ diff --git a/backlog/tasks/task-70 - Overlay-runtime-refactor-remove-invisible-mode-and-bind-visible-overlay-to-mpv-subtitles.md b/backlog/completed/task-70 - Overlay-runtime-refactor-remove-invisible-mode-and-bind-visible-overlay-to-mpv-subtitles.md similarity index 100% rename from backlog/tasks/task-70 - Overlay-runtime-refactor-remove-invisible-mode-and-bind-visible-overlay-to-mpv-subtitles.md rename to backlog/completed/task-70 - Overlay-runtime-refactor-remove-invisible-mode-and-bind-visible-overlay-to-mpv-subtitles.md diff --git a/backlog/tasks/task-72 - macOS-config-validation-UX-show-full-warning-details-in-native-dialog.md b/backlog/completed/task-72 - macOS-config-validation-UX-show-full-warning-details-in-native-dialog.md similarity index 100% rename from backlog/tasks/task-72 - macOS-config-validation-UX-show-full-warning-details-in-native-dialog.md rename to backlog/completed/task-72 - macOS-config-validation-UX-show-full-warning-details-in-native-dialog.md diff --git a/backlog/tasks/task-73 - MPV-plugin-split-into-modules-and-optimize-startup-command-runtime.md b/backlog/completed/task-73 - MPV-plugin-split-into-modules-and-optimize-startup-command-runtime.md similarity index 100% rename from backlog/tasks/task-73 - MPV-plugin-split-into-modules-and-optimize-startup-command-runtime.md rename to backlog/completed/task-73 - MPV-plugin-split-into-modules-and-optimize-startup-command-runtime.md diff --git a/backlog/tasks/task-74 - Startup-warmups-configurable-warmup-vs-defer-with-low-power-mode.md b/backlog/completed/task-74 - Startup-warmups-configurable-warmup-vs-defer-with-low-power-mode.md similarity index 100% rename from backlog/tasks/task-74 - Startup-warmups-configurable-warmup-vs-defer-with-low-power-mode.md rename to backlog/completed/task-74 - Startup-warmups-configurable-warmup-vs-defer-with-low-power-mode.md diff --git a/backlog/tasks/task-75 - Tokenizer-configurable-POS-exclusions-for-N1-and-frequency-annotations.md b/backlog/completed/task-75 - Tokenizer-configurable-POS-exclusions-for-N1-and-frequency-annotations.md similarity index 100% rename from backlog/tasks/task-75 - Tokenizer-configurable-POS-exclusions-for-N1-and-frequency-annotations.md rename to backlog/completed/task-75 - Tokenizer-configurable-POS-exclusions-for-N1-and-frequency-annotations.md diff --git a/backlog/tasks/task-76 - Tokenizer-remove-POS-exclusion-config-surface-and-keep-hardcoded-defaults.md b/backlog/completed/task-76 - Tokenizer-remove-POS-exclusion-config-surface-and-keep-hardcoded-defaults.md similarity index 100% rename from backlog/tasks/task-76 - Tokenizer-remove-POS-exclusion-config-surface-and-keep-hardcoded-defaults.md rename to backlog/completed/task-76 - Tokenizer-remove-POS-exclusion-config-surface-and-keep-hardcoded-defaults.md diff --git a/backlog/tasks/task-78 - Launcher-and-mpv-plugin-auto-start-pause-until-ready-and-single-start-guard.md b/backlog/completed/task-78 - Launcher-and-mpv-plugin-auto-start-pause-until-ready-and-single-start-guard.md similarity index 100% rename from backlog/tasks/task-78 - Launcher-and-mpv-plugin-auto-start-pause-until-ready-and-single-start-guard.md rename to backlog/completed/task-78 - Launcher-and-mpv-plugin-auto-start-pause-until-ready-and-single-start-guard.md diff --git a/backlog/tasks/task-79 - Jimaku-modal-auto-close-after-successful-subtitle-load.md b/backlog/completed/task-79 - Jimaku-modal-auto-close-after-successful-subtitle-load.md similarity index 100% rename from backlog/tasks/task-79 - Jimaku-modal-auto-close-after-successful-subtitle-load.md rename to backlog/completed/task-79 - Jimaku-modal-auto-close-after-successful-subtitle-load.md diff --git a/backlog/tasks/task-80 - Jimaku-download-rename-subtitle-to-match-current-video.md b/backlog/completed/task-80 - Jimaku-download-rename-subtitle-to-match-current-video.md similarity index 100% rename from backlog/tasks/task-80 - Jimaku-download-rename-subtitle-to-match-current-video.md rename to backlog/completed/task-80 - Jimaku-download-rename-subtitle-to-match-current-video.md diff --git a/backlog/tasks/task-81 - Tokenization-performance-disable-yomitan-mecab-and-persistent-local-mecab.md b/backlog/completed/task-81 - Tokenization-performance-disable-yomitan-mecab-and-persistent-local-mecab.md similarity index 100% rename from backlog/tasks/task-81 - Tokenization-performance-disable-yomitan-mecab-and-persistent-local-mecab.md rename to backlog/completed/task-81 - Tokenization-performance-disable-yomitan-mecab-and-persistent-local-mecab.md diff --git a/backlog/tasks/task-82 - Subtitle-frequency-highlighting-fixes-for-noisy-readings-and-known-token-color-priority.md b/backlog/completed/task-82 - Subtitle-frequency-highlighting-fixes-for-noisy-readings-and-known-token-color-priority.md similarity index 100% rename from backlog/tasks/task-82 - Subtitle-frequency-highlighting-fixes-for-noisy-readings-and-known-token-color-priority.md rename to backlog/completed/task-82 - Subtitle-frequency-highlighting-fixes-for-noisy-readings-and-known-token-color-priority.md diff --git a/backlog/tasks/task-83 - Jellyfin-subtitle-delay-shift-to-adjacent-cue-without-seek-jumps.md b/backlog/completed/task-83 - Jellyfin-subtitle-delay-shift-to-adjacent-cue-without-seek-jumps.md similarity index 100% rename from backlog/tasks/task-83 - Jellyfin-subtitle-delay-shift-to-adjacent-cue-without-seek-jumps.md rename to backlog/completed/task-83 - Jellyfin-subtitle-delay-shift-to-adjacent-cue-without-seek-jumps.md diff --git a/backlog/tasks/task-133 - Improve-AniList-character-dictionary-parity-with-upstream-guide.md b/backlog/tasks/task-133 - Improve-AniList-character-dictionary-parity-with-upstream-guide.md index 057d47e..f45c82c 100644 --- a/backlog/tasks/task-133 - Improve-AniList-character-dictionary-parity-with-upstream-guide.md +++ b/backlog/tasks/task-133 - Improve-AniList-character-dictionary-parity-with-upstream-guide.md @@ -1,10 +1,11 @@ --- id: TASK-133 title: Improve AniList character dictionary parity with upstream guide -status: To Do -assignee: [] +status: In Progress +assignee: + - OpenCode created_date: '2026-03-08 21:06' -updated_date: '2026-03-08 21:35' +updated_date: '2026-03-10 06:18' labels: - dictionary - anilist @@ -28,15 +29,11 @@ priority: high ## Description - Plan and implement guide-faithful parity improvements for the AniList character dictionary flow inside SubMiner's current single-media generation path. Scope includes AniList first/last name hints, hint-aware reading generation for kanji/native names, expanded honorific coverage, 160x200 JPEG thumbnail handling, and AniList 429 retry/backoff behavior. - ## Acceptance Criteria - - - [ ] #1 AniList character queries include first/last name fields and preserve them through runtime data models. - [ ] #2 Dictionary generation uses hint-aware name splitting and reading generation for kanji and mixed native names, not only kana-only readings. - [ ] #3 Honorific generation is expanded substantially toward upstream coverage and is covered by regression tests. @@ -44,10 +41,20 @@ Plan and implement guide-faithful parity improvements for the AniList character - [ ] #5 AniList requests handle 429 responses with bounded exponential backoff and tests cover retry behavior. +## Implementation Plan + + +1. Add targeted regression tests in `src/main/character-dictionary-runtime.test.ts` for AniList 429 retry behavior and thumbnail conversion/fallback image paths before changing runtime code. +2. Update `fetchAniList` in `src/main/character-dictionary-runtime.ts` to retry 429 responses with bounded exponential backoff or `Retry-After` delays, while preserving existing request pacing hook behavior. +3. Update `downloadCharacterImage` to run downloaded bytes through `convertImageToThumbnail`, returning `.jpg` paths when conversion succeeds and preserving original bytes/extensions on fallback. +4. Run focused character-dictionary tests first, then broader required verification commands if the focused lane passes. +5. Record progress against acceptance criteria #4 and #5 in TASK-133 notes/final checklist once verified. + + ## Implementation Notes - Approved design and implementation plan captured on 2026-03-08. Scope stays within current single-media AniList dictionary flow; excludes username-driven CURRENT-list fetching and Yomitan auto-update schema work. +Resumed TASK-133 to finish remaining AC #4 image thumbnail wiring and AC #5 AniList 429 retry handling. Existing code already contains helper constants/functions; next work is wiring plus regression coverage. diff --git a/backlog/tasks/task-151 - Keep-JLPT-underline-color-stable-during-Yomitan-text-selection.md b/backlog/tasks/task-151 - Keep-JLPT-underline-color-stable-during-Yomitan-text-selection.md new file mode 100644 index 0000000..2601ed2 --- /dev/null +++ b/backlog/tasks/task-151 - Keep-JLPT-underline-color-stable-during-Yomitan-text-selection.md @@ -0,0 +1,99 @@ +--- +id: TASK-151 +title: Keep JLPT underline color stable during Yomitan text selection +status: Done +assignee: + - OpenCode +created_date: '2026-03-10 06:42' +updated_date: '2026-03-10 07:54' +labels: [] +dependencies: [] +references: + - src/renderer/style.css + - src/renderer/subtitle-render.test.ts +documentation: + - ../subminer-docs/development.md + - ../subminer-docs/architecture.md +priority: medium +--- + +## Description + + +Fix the subtitle overlay so JLPT underlines keep their JLPT color when Yomitan lookups trigger hover/selection styling on tokens that are also known, N+1, or frequency-highlighted. + + +## Acceptance Criteria + +- [x] #1 JLPT-tagged subtitle tokens keep their JLPT underline color during hover/selection states used by Yomitan lookup. +- [x] #2 Tokens that combine JLPT with known, N+1, name-match, or frequency styling preserve their primary text color while keeping the JLPT underline color. +- [x] #3 Renderer regression coverage verifies the JLPT underline color remains explicit in the stylesheet for combined styling states. + + +## Implementation Plan + + +1. Revert the temporary box-shadow JLPT marker experiment and restore native JLPT underline styling in `src/renderer/style.css`. +2. Add explicit JLPT hover/selection color rules with higher specificity so Yomitan lookup highlight states cannot repaint the underline to the token hover color. +3. Update `src/renderer/subtitle-render.test.ts` to verify JLPT hover/selection rules keep the predefined JLPT underline color, then rerun focused and relevant verification commands. + +4. Add JLPT-specific `text-decoration-color` overrides directly on child `.c` spans and their hover/selection states, since Yomitan lookup likely paints the decorated child text runs rather than only the parent token span. + + +## Implementation Notes + + +Added a renderer regression test that asserts JLPT underline color stays explicit through hover and selection styling. + +Updated `src/renderer/style.css` to route JLPT underline color through `--subtitle-jlpt-underline-color` and re-apply it on word/character hover and selection states. + +Verification: `bun test src/renderer/subtitle-render.test.ts`, `bun run typecheck`, `bun run test:fast`, `bun run build`, `bun run changelog:lint`, `bunx prettier --check src/renderer/style.css src/renderer/subtitle-render.test.ts changes/jlpt-underline-yomitan.md`. `bun run format:check:src` still fails because of unrelated existing formatting issues in `src/main-entry-runtime.test.ts` and `src/main-entry-runtime.ts`. + +User verified the original CSS variable propagation fix did not resolve the live issue; continuing investigation and reopening the task. + +Confirmed Chromium was repainting native `text-decoration` underlines with the selected text color; reproduced it in a temporary browser repro and verified that switching the JLPT marker to an inset box-shadow keeps the JLPT color stable during selection. + +Replaced the previous native underline approach with a fragment-safe JLPT marker using `box-shadow` plus `box-decoration-break`, then updated stylesheet regression coverage to match the new implementation. + +Verification: `bun test src/renderer/subtitle-render.test.ts`, `bunx prettier --check src/renderer/style.css src/renderer/subtitle-render.test.ts`, `bun run typecheck`, `bun run test:fast`, `bun run build`. + +User requested reverting the box-shadow marker experiment. Narrowing the fix to preserve native JLPT underline color during Yomitan hover/selection instead of changing the underline rendering method. + +Reverted the box-shadow experiment per user request. Restored native JLPT underline styling and added explicit JLPT hover/selection rules so Yomitan lookup states keep the predefined JLPT underline color with `!important`. + +Verification after revert/fix: `bun test src/renderer/subtitle-render.test.ts`, `bunx prettier --check src/renderer/style.css src/renderer/subtitle-render.test.ts`, `bun run typecheck`, `bun run build`. + +Issue still reproduces specifically during Shift-held Yomitan lookup. Investigating decoration propagation onto child `.c` spans during lookup-triggered hover/selection states. + +Added JLPT-specific `text-decoration-color` overrides directly to child `.c` spans and their hover/selection states, in addition to the token span itself, so lookup-triggered decoration painting on child runs keeps the JLPT color. + +Verification: `bun test src/renderer/subtitle-render.test.ts`, `bunx prettier --check src/renderer/style.css src/renderer/subtitle-render.test.ts`, `bun run typecheck`, `bun run build`. + +Added a final high-specificity combined-state JLPT override after the annotation hover/selection rules. It targets tokens that are both JLPT-tagged and known/N+1/name-match/frequency-highlighted, forcing `text-decoration-color` back to the JLPT variable after the later annotation state blocks. + +Verification: `bun test src/renderer/subtitle-render.test.ts`, `bunx prettier --check src/renderer/style.css src/renderer/subtitle-render.test.ts`, `bun run build`. + +Switched the JLPT underline from native `text-decoration` to a separate painted underline layer using `background-image` on JLPT tokens. Also changed hover token highlight rules to use `background-color` so the JLPT underline layer survives Shift+Yomitan hover/lookup interactions. + +Verification: `bun test src/renderer/subtitle-render.test.ts`, `bunx prettier --check src/renderer/style.css src/renderer/subtitle-render.test.ts`, `bun run build`. + +User reports the latest background-image JLPT underline change introduced grey-looking token highlights. Investigating the interaction between the new underline layer and hover background styling. + +The background-image underline layer interacts poorly with hover fills and makes highlighted tokens look grey. Switching the separate JLPT underline layer to an inset box-shadow so hover background color and JLPT underline paint can coexist cleanly. + +Adjusted the separate JLPT underline layer from `background-image` to inset `box-shadow`. This keeps the underline decoupled from Yomitan hover color while avoiding the grey-looking underline artifact introduced by the background-image approach. + +User reports the separate underline layer still disappears/greys out during lookup. Switching strategy again: use a dedicated `border-bottom` JLPT marker instead of box-shadow/background so the underline stays independent from Yomitan hover paint without blending artifacts. + +Replaced the separate JLPT underline layer with `border-bottom` plus a small bottom padding. This keeps the underline visually separate from Yomitan hover paint and avoids the disappearing/grey artifact seen with background-image and box-shadow. + + +## Final Summary + + +Switched the JLPT underline layer in `src/renderer/style.css` to `border-bottom` plus `padding-bottom`, keeping it independent from Yomitan hover/selection repainting while avoiding the disappearing or grey artifact seen with the background-image and box-shadow approaches. + +Updated `src/renderer/subtitle-render.test.ts` to assert the border-bottom-based JLPT underline layer. + +Verification: `bun test src/renderer/subtitle-render.test.ts`, `bunx prettier --check src/renderer/style.css src/renderer/subtitle-render.test.ts`, `bun run build`. + diff --git a/package.json b/package.json index 44f68ad..30f4e3b 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "test:launcher": "bun run test:launcher:src", "test:core": "bun run test:core:src", "test:subtitle": "bun run test:subtitle:src", - "test:fast": "bun run test:config:src && bun run test:core:src && bun test src/main-entry-runtime.test.ts src/anki-integration/anki-connect-proxy.test.ts src/release-workflow.test.ts src/ci-workflow.test.ts scripts/build-changelog.test.ts && bun run tsc && bun test dist/main/runtime/registry.test.js", + "test:fast": "bun run test:config:src && bun run test:core:src && bun test src/main-entry-runtime.test.ts src/anki-integration/anki-connect-proxy.test.ts src/release-workflow.test.ts src/ci-workflow.test.ts scripts/build-changelog.test.ts scripts/mkv-to-readme-video.test.ts && bun run tsc && bun test dist/main/runtime/registry.test.js", "generate:config-example": "bun run build && bun dist/generate-config-example.js", "start": "bun run build && electron . --start", "dev": "bun run build && electron . --start --dev", diff --git a/scripts/mkv-to-readme-video.sh b/scripts/mkv-to-readme-video.sh index 17769b7..04fbd6a 100755 --- a/scripts/mkv-to-readme-video.sh +++ b/scripts/mkv-to-readme-video.sh @@ -102,7 +102,19 @@ fi has_encoder() { local encoder="$1" - ffmpeg -hide_banner -encoders 2> /dev/null | grep -qE "[[:space:]]${encoder}[[:space:]]" + ffmpeg -hide_banner -encoders 2> /dev/null | awk -v encoder="$encoder" '$2 == encoder { found = 1 } END { exit(found ? 0 : 1) }' +} + +pick_webp_encoder() { + if has_encoder "libwebp_anim"; then + echo "libwebp_anim" + return 0 + fi + if has_encoder "libwebp"; then + echo "libwebp" + return 0 + fi + return 1 } crop_vf="crop=1920:1080:760:205" @@ -168,14 +180,14 @@ else fi if [[ "$generate_webp" -eq 1 ]]; then - if ! has_encoder "libwebp"; then + if ! webp_encoder="$(pick_webp_encoder)"; then echo "Error: encoder not found: libwebp" >&2 exit 1 fi - echo "Generating animated WebP: $webp_out" + echo "Generating animated WebP with $webp_encoder: $webp_out" ffmpeg "$overwrite_flag" -i "$input" \ -vf "${crop_vf},fps=24,scale=960:-1:flags=lanczos" \ - -c:v libwebp \ + -c:v "$webp_encoder" \ -q:v 80 \ -loop 0 \ -an \ diff --git a/scripts/mkv-to-readme-video.test.ts b/scripts/mkv-to-readme-video.test.ts new file mode 100644 index 0000000..6e56877 --- /dev/null +++ b/scripts/mkv-to-readme-video.test.ts @@ -0,0 +1,75 @@ +import assert from 'node:assert/strict'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import { spawnSync } from 'node:child_process'; +import test from 'node:test'; + +function withTempDir(fn: (dir: string) => T): T { + const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'subminer-mkv-to-readme-video-test-')); + try { + return fn(dir); + } finally { + fs.rmSync(dir, { recursive: true, force: true }); + } +} + +function writeExecutable(filePath: string, contents: string): void { + fs.writeFileSync(filePath, contents, 'utf8'); + fs.chmodSync(filePath, 0o755); +} + +test('mkv-to-readme-video accepts libwebp_anim when libwebp is unavailable', () => { + withTempDir((root) => { + const binDir = path.join(root, 'bin'); + const inputPath = path.join(root, 'sample.mkv'); + const ffmpegLogPath = path.join(root, 'ffmpeg-args.log'); + + fs.mkdirSync(binDir, { recursive: true }); + fs.writeFileSync(inputPath, 'fake-video', 'utf8'); + + writeExecutable( + path.join(binDir, 'ffmpeg'), + `#!/usr/bin/env bash +set -euo pipefail + +if [[ "$#" -ge 2 && "$1" == "-hide_banner" && "$2" == "-encoders" ]]; then + cat <<'EOF' + V....D libx264 libx264 H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10 (codec h264) + V....D libvpx-vp9 libvpx VP9 (codec vp9) + V....D libwebp_anim libwebp WebP image (codec webp) + A....D aac AAC (Advanced Audio Coding) + A....D libopus libopus Opus (codec opus) +EOF + exit 0 +fi + +printf '%s\\n' "$*" >> "${ffmpegLogPath}" +output="" +for arg in "$@"; do + output="$arg" +done +mkdir -p "$(dirname "$output")" +touch "$output" +`, + ); + + const result = spawnSync('bash', ['scripts/mkv-to-readme-video.sh', '--webp', inputPath], { + cwd: process.cwd(), + env: { + ...process.env, + PATH: `${binDir}:${process.env.PATH || ''}`, + }, + encoding: 'utf8', + }); + + assert.equal(result.status, 0, result.stderr || result.stdout); + assert.equal(fs.existsSync(path.join(root, 'sample.mp4')), true); + assert.equal(fs.existsSync(path.join(root, 'sample.webm')), true); + assert.equal(fs.existsSync(path.join(root, 'sample.webp')), true); + assert.equal(fs.existsSync(path.join(root, 'sample-poster.jpg')), true); + + const ffmpegLog = fs.readFileSync(ffmpegLogPath, 'utf8'); + assert.match(ffmpegLog, /-c:v libwebp_anim/); + }); +});