* refactor(main): move the YouTube domain into an assembly
createYoutubeAssembly owns the YouTube flow, playback, media cache,
whisper subtitles, primary-subtitle notifications, and the in-app
browser, plus the browser quit-on-close state. main.ts builds it once
where the first YouTube runtime used to be and calls through the result.
Siblings built later are passed as getters or callbacks; tests pin that
construction calls none of them.
* docs(architecture): describe per-domain assemblies
* test(e2e): blank the line before mining the sub-delay cue
The previous test leaves the same cue on screen, so the overlay check
passed before the seek landed and --mine-sentence could read mpv's empty
mid-seek sub-text and skip the card.
* refactor(main): move the Jellyfin domain into an assembly
createJellyfinAssembly owns the Jellyfin composer call, its playback and
remote-progress state, the subtitle cache IO, mpv defaults, and the tray
discovery deps. main.ts builds it where the composer call used to be and
reads state through getActivePlayback/getQuitOnDisconnectArmed.
* refactor(main): move the AniList domain into an assembly
createAnilistAssembly owns the AniList setup and tracking composer calls,
the setup window, the state runtime, and the token, media-guess, in-flight
and attempted-update state they share. main.ts builds it where the setup
composer call used to be and keeps the rate limiter, which the stats
cover-art fetcher also uses.
* refactor(main): move config hot-reload, settings, and sync UI into assemblies
createSettingsAssembly owns config hot-reload and the settings window,
which share the hot-reload-applied handler; main.ts still supplies that
handler's cross-domain deps and the Yomitan deck-name helper.
createSyncAssembly owns the sync window, sync IPC runtime, and auto-sync
scheduler. Both are built where their blocks were; main.ts keeps the
registerHandlers()/start() calls at the same points.
* refactor(main): move dictionary runtimes into assemblies
createDictionariesAssembly owns the JLPT and frequency lookups and their
dictionary-root builders; main.ts still resolves the root paths at the
same point. createCharacterDictionaryAssembly owns the character
dictionary runtime, Yomitan auto-sync, and the portrait and name-candidate
lookups. Both are built where their blocks were; later siblings (Yomitan
profile policy, notifications, OSD, subtitle refresh) come in as getters
or callbacks.
* refactor(main): move overlay modal openers into an assembly
createOverlayModalsAssembly owns the shared open-with-notification helper
and the runtime options, Jimaku, TsukiHime, subtitle generation, session
help, changelog, character dictionary manager, controller, and playlist
browser openers. main.ts builds it where the openers used to live.
* refactor(logger): export the Logger type for assembly deps
The build emits declarations, and the Jellyfin assembly returns the logger
in its tray discovery deps, so tsc needs a nameable Logger type. Assemblies
now Pick from it directly.
* refactor(main): drop remaining pass-through main-deps builders
Jellyfin client-info/setup-window/remote, overlay shortcuts/visibility/
layout/window-factory, config hot-reload, and startup bootstrap factory
builders only forwarded deps. Callers now pass deps to the handlers
directly; createWatchConfigPathHandler moves into config-hot-reload-handlers.
* refactor(main): fold small-logic main-deps builders into their handlers
Tray click, mpv client factory, startup bootstrap exit callbacks, overlay
runtime options, and dictionary roots/runtime builders move their logic
into the owning handler or call site. The two duplicated dictionary-root
builders become one createBuildDictionaryRootsHandler(vendorDirName).
* refactor(main): fold cli command context builders into the factory
createCliCommandContextFactory now adapts appState and main services to
the CLI command context deps directly (socket-path change notification,
texthooker port/websocket URL, browser-open default). The pass-through
cli-command-context-main-deps and cli-command-context-deps layers are
removed; their behavioural tests now run against the factory's context.
* refactor(main): fold subtitle tokenization builder into the mpv composer
The tokenizer deps builder's logic (known-word lookup recording and the
character-dictionary gate on name matching) becomes
buildTokenizerRuntimeOptions in mpv-runtime-composer, tested through the
composer. The MeCab check and dictionary prewarm handlers move to
subtitle-tokenization-warmup.ts, and the unused BuiltMainDeps type is
removed from composer contracts.
* refactor(main): drop redundant tokenizer deps keys and cast
* refactor(main): end the main-deps builder pattern
mpv-main-event-main-deps becomes mpv-main-event-binding-deps
(createMpvMainEventBindingDeps), returning the binding deps directly
instead of a thunk; it owns the subtitle-recording state. The will-quit
cleanup adapter likewise returns its deps directly, and the pass-through
app-lifecycle activate builders are removed.
* docs: drop references to main-deps builders
* refactor(main): drop mining/anki main-deps builders
* refactor(anilist): drop main-deps builders
* refactor(jellyfin): drop main-deps builders
* refactor(overlay): drop overlay and shortcut main-deps builders
* refactor(main): drop ipc, tray, and osd main-deps builders
* refactor(startup): drop startup main-deps builders
* test(scripts): ignore stray local requests in AUR download test
Also apply prettier to launcher/mpv.test.ts so format:check:src passes.
* fix(shortcuts): forward overlay numeric selection hooks through the composer
composeShortcutRuntimes only passed the digit callbacks, so the visible-overlay
selection added in #78 never ran from the multi-copy/mine-sentence shortcuts.
* fix(stats): skip mpv auto-connect when stats starts the immersion tracker
The immersion-startup main-deps builder never forwarded shouldAutoConnectMpv,
so background stats and stats CLI startup always connected to mpv. Drop the
builder and pass the deps straight to the handler.
* build(deps): bump follow-redirects override to 1.16.2
Resolves GHSA-mrwp-f3q8-3g9p (high), which fails the quality-gate
bun audit --audit-level high step.
* test(subtitles): cover the null-tokenization settle path in prefetch test
The mock ignored the value the test resolved, so the null case never ran.
* test(e2e): surface mpv crashes and install current mpv on macOS CI
The harness IPC client never handled socket close, so a crashed mpv left
commands pending until the 120s test timeout with an empty mpv.log. Reject
pending commands on close, write mpv's own log via --log-file, and refresh
Homebrew on macOS CI so the installed mpv build no longer depends on the
runner image.
* test(e2e): render mpv through cocoa-cb OpenGL on macOS
mpv 0.41.0_12 with libplacebo 7.387.0 segfaults in gpu-next's MoltenVK path
on GitHub's paravirtual macOS GPU on the first frame, so the E2E session
never started.
* test(e2e): name mpv-stdio.log in the mpv close diagnostic
* feat(subtitles): generate Japanese subtitles during Jellyfin playback
- Read the item's original file via toJellyfinOriginalStreamUrl and write output to a temp dir
- Transcoded playback uses Jellyfin's audio index and only external references
- Show Jellyfin media by title and redact URL tokens in subprocess errors
- Update overlay copy, docs, and changelog fragment
* fix(subtitles): redact full stream tokens and reuse one Jellyfin output folder
* test(subtitles): make Jellyfin output path assertions platform-neutral
- Compare dirname/basename via node:path instead of POSIX-only regexes
- Assert the full sub-add command uses the generated output path
* refactor: remove dead code and enable noUnusedLocals
- delete launcher/jimaku.ts and the launcher Args/config jimaku* fields
that only fed it (the SUBMINER_JIMAKU_* env vars had no effect)
- delete plugin/subminer/hover.lua; nothing has sent subminer-hover-token
since Overlay 2.0
- drop never-read anilist.characterDictionary.refreshTtlHours and
evictionPolicy (still hidden in the settings UI for old configs)
- drop unused root deps: eslint, @fontsource-variable/geist{,-mono}
(stats/ declares its own)
- delete the committed draft renderer-performance plan doc
- turn on noUnusedLocals for typecheck and remove what it surfaced,
including Windows focus-handoff values computed but never consumed
* docs(changes): add docs fragment and note retired hover messages
* test: add headless end-to-end harness and CI job
Boot the built app against a real mpv, a synthetic clip, a fixture Yomitan
dictionary, and a fake AnkiConnect inside a private Xvfb display, then assert
on the overlay DOM and the AnkiConnect requests. Adds the test:e2e lane, an
e2e session CLI for exploratory checks, and a three-platform CI job.
* ci: set e2e temp dir from a step
* test: make e2e session startup robust on slow machines
Bun caps node:test hooks at 5s, so boot the session inside the first test.
Keep pausing until the app's resume retries stop, read cue tokens in one
round trip, and resolve Electron through its package entry so CI downloads
the binary.
* test: harden e2e harness for CI runners
Fail fast when mpv cannot be spawned, read every Yomitan popup frame instead
of trusting target order, and put the chocolatey mpv directory on PATH.
* test: add e2e failure diagnostics and mark Windows job experimental
Report what the overlay showed when a cue never renders, dispose the session
when the overlay cannot be opened, skip popup frames that vanish mid-poll,
and turn off AniSkip lookups in the harness config.
* test: read the lookup popup entry from textContent
* test: capture renderer console and main-side subtitle state in e2e failures
* test: expose the Electron main-process inspector to the e2e harness
Adds session.mainProcess() and 'e2e eval main', and reports main's window
list alongside the overlay state when a cue never renders.
* test: tap subtitle sends and receipts in e2e failure diagnostics
* test: probe alternate main-to-renderer paths in e2e failure diagnostics
* test: select the visible overlay layer, not the primed modal window
Windows and macOS pre-create a hidden modal window that loads the same
renderer document, and the harness could attach to it instead of the
visible overlay. Drops the delivery taps that found this.
* ci: run the Windows e2e job as a normal matrix entry
* test: address review on e2e harness
Slice the app log at a byte offset before decoding, create the session state
file owner-only since it carries the inspector URL, and keep the cue failure
when inspector diagnostics are unavailable.
* test: create the e2e session state file exclusively
* fix(overlay): reload imported mpv keys after mpv connects
The overlay fetched mpv's input-bindings before SubMiner connected to mpv,
cached an empty list, and never refetched because the connect-time refresh
only broadcast session bindings when their compiled signature changed. Keys
from input.conf and mpv defaults (9/0 volume, m mute) then did nothing while
the overlay had focus, most often in mpv.backend x11 mode.
Main now emits mpv-input-bindings:changed whenever the discovered mpv key
set or client changes, and the renderer refreshes its imported keys on it.
* fix(overlay): subscribe to mpv key changes before initial discovery
The renderer registered the mpv-input-bindings:changed listener only after awaiting setupMpvInputForwarding and several other startup calls. If mpv connected in that window, the event was dropped and the imported mpv keys stayed empty.
- Preview session observes time-pos, waits for loaded media before seeking, and acks each command
- Forward positions to the modal over media-timing-review:preview-position
- Cursor follows the real position, pauses while buffering, and stays at the clip end when done
- Replace the fixed end-of-clip timer with a 15s stall timeout
- Document preview cursor behavior and ownership in docs
- Drop the afterAllArtifactBuild size reporting hook and the previous-release baseline downloads in CI
- Stop uploading, checksumming, and publishing package-size-*.json
- Keep the afterPack package content audit, now without size tracking
- Update RELEASING.md and the verification docs, and add a changelog fragment
- Reconstruct fragment-only karaoke while preserving canonical animated signs
- Reprocess live subtitles after initial playback and seek updates
- Add a development launcher for playable media files