Files
SubMiner/docs/architecture/domains.md
T
sudacode 2924f04316 refactor(main): move domains into per-domain assemblies (#309)
* 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.
2026-10-31 19:39:27 -07:00

8.6 KiB

Domain Ownership

Status: active Last verified: 2026-07-15 Owner: Kyle Yasuda Read when: you need to find the owner module for a behavior or test surface

Runtime Domains

  • Desktop app runtime: src/main.ts, src/main/, src/core/services/
  • Overlay renderer: src/renderer/
  • Launcher CLI: launcher/
  • mpv plugin: plugin/subminer/

Product / Integration Domains

  • Config system: src/config/; Anki resolution is composed by src/config/resolve/anki-connect.ts from focused resolvers in src/config/resolve/anki-connect/ Config hot-reload and the settings window: src/main/assemblies/settings.ts
  • Overlay/window state: src/core/services/overlay-*, src/main/overlay-*.ts Overlay-hosted modal openers: src/main/assemblies/overlay-modals.ts
  • MPV runtime and protocol: src/core/services/mpv*.ts Windows executable lookup and detached process creation are shared in src/main/runtime/mpv-process.ts. The Windows launcher and Jellyfin handlers retain their own playback and connection workflows.
  • Subtitle/token pipeline: src/core/services/subtitle-*.ts, src/core/services/tokenizer*, src/core/services/tokenizer/, src/subsync/ JLPT and frequency annotation lookups: src/main/jlpt-runtime.ts, src/main/frequency-dictionary-runtime.ts, assembled by src/main/assemblies/dictionaries.ts
  • Anki workflow: src/anki-integration/, src/core/services/anki-jimaku*.ts
  • Media timing review: src/main/runtime/media-timing-review.ts owns the review and hidden MediaTimingPreviewSession. The session waits for mpv readiness and acknowledges playback commands; its time-pos observations reach the modal through media-timing-review:preview-position. The renderer places the cursor from those timestamps and uses an inactivity timeout instead of timing the clip itself.
  • Immersion tracking: src/core/services/immersion-tracker/ Includes stats storage/query schema such as imm_videos, imm_media_art, and imm_youtube_videos for per-video and YouTube-specific library metadata. Library-entry identity aliases and merge recommendations are persisted alongside this schema; the stats HTTP and SPA layers only expose and present those domain decisions. delete-maintenance-scheduler.ts coalesces and serializes stats deletes; the expensive work runs in delete-maintenance-worker-thread.ts while the tracker queues playback writes. Each batch uses one transaction, lexical update, rollup refresh, and incremental lifetime subtraction (planLifetimeRemovals/applyLifetimeRemovals in lifetime.ts). Merges, moves, AniList reassignments, and stats cleanup -l use repairLifetimeSummariesFromMedia (recompute from the per-video media ledger). The full lifetime rebuild survives only as the empty-table bootstrap; anywhere else it would collapse lifetime totals to the session retention window.
  • Immersion sync: src/core/services/stats-sync/, bound by src/main/sync-cli.ts. The sync window and auto-sync scheduler live in src/main/assemblies/sync.ts. snapshot-transfer.ts selects compressed rsync or scp. transfer-cache.ts atomically retains the last successfully received snapshot per hashed peer/database identity under the config directory's sync-transfer-cache/. Cache copies seed isolated transfer directories; rsync verifies reconstructed files before the existing merge engine runs. The --make-temp / --remove-temp helpers accept an internal --transfer-cache key, with a fallback for older peers that do not recognize it.
  • AniList tracking + character dictionary: src/core/services/anilist/, src/main/runtime/composers/anilist-* (called by src/main/assemblies/anilist.ts), src/main/character-dictionary-runtime.ts, src/main/character-dictionary-runtime/, assembled by src/main/assemblies/character-dictionary.ts
  • TMDB live-action metadata: src/core/services/tmdb/ (client + exact-title resolver), src/core/services/immersion-tracker/live-action-link.ts (links an entry to a TMDB title and merges other holders of the same title). The AniList cover-art fetcher calls the resolver as its fallback; imm_anime.media_kind marks the result and keeps the entry out of AniList season repair.
  • Jellyfin integration: src/core/services/jellyfin*.ts, src/main/runtime/composers/jellyfin-*, assembled by src/main/assemblies/jellyfin.ts
  • YouTube playback, subtitles, media cache, and in-app browser: src/core/services/youtube/, src/main/runtime/youtube-*, assembled by src/main/assemblies/youtube.ts
  • User plugins: src/core/services/plugins/ (host, ctx, exec), src/main/runtime/plugin-panel-runtime.ts (panel modal), src/main/runtime/plugin-window-runtime.ts (plugin windows), src/renderer/modals/plugin-panel*.ts; examples in assets/plugins/
  • Window trackers: src/window-trackers/
  • Stats HTTP app: src/core/services/stats-server.ts, with route groups and shared route support in src/core/services/stats-server/
  • Stats SPA: stats/
  • Public docs site: docs-site/

Shared Contract Entry Points

Automatic mpv keyboard discovery uses the get-mpv-input-bindings IPC request and MpvInputBindingsSnapshot in src/types/session-bindings.ts. src/main/runtime/mpv-input-bindings.ts queries the connected player and preserves configured keys, including disabled entries. src/shared/mpv-input-bindings.ts validates discovered keys and translates browser input. The renderer's handlers/mpv-input-forwarding.ts keeps the session lookup, coalesces asynchronous refreshes, and releases held keys on blur or disposal. Discovered WHEEL_* bindings forward as keypress <key> <notches> (Chromium reports 120 px per notch), which matches mpv's own precise-scroll scaling. MBTN_* buttons forward as held keydown/keyup when mpv binds the button or its _DBL variant, so mpv synthesizes double-clicks itself. Configured keybindings and the built-in right-click pause run first. Forwarding is the only way pointer input reaches mpv on Hyprland, where the overlay cannot be made click-through. handlers/keyboard.ts runs this fallback after SubMiner controls and refreshes on startup, a delayed startup pass, focus, binding reload, and the mpv-input-bindings:changed event. Main sends that event from session-bindings-runtime.ts when mpv's discovered key set changes, including the first discovery after connecting, because the overlay often loads before mpv connects and the compiled session bindings may not change. Discovery does not enter compiled session bindings, the plugin artifact, persistent config, or session help.

The subtitle sidebar consumes parsed cues through SubtitleSidebarSnapshot. Its sourceKey identifies the media and subtitle source so renderer selections are invalidated on source changes, including changes whose cue text and timings are identical. While open, the sidebar polls the cheap SubtitleSidebarPlaybackState (cached time, current line, sourceKey, cuesRevision; no mpv round-trips) every 80 ms for active-cue tracking. It fetches the full snapshot, which resolves the source over mpv IPC and carries every cue plus config, only on open, config reload, a sourceKey/cuesRevision change, and a ~2 s fallback. Native selection and clean clipboard serialization live in src/renderer/modals/subtitle-sidebar-selection.ts. Electron lets standard Copy input reach the renderer, where sidebar selection takes priority over the live-subtitle binding. The preload bridge writes selections through Electron's clipboard API so copying does not depend on Chromium document focus or require activating the overlay window.

  • Config + app-state contracts: src/types/config.ts
  • Subtitle/token/media annotation contracts: src/types/subtitle.ts
  • Runtime/window/controller/Electron bridge contracts: src/types/runtime.ts
  • Anki-specific contracts: src/types/anki.ts
  • External integration contracts: src/types/integrations.ts
  • Runtime-option contracts: src/types/runtime-options.ts
  • Settings UI contracts: src/types/settings.ts
  • Session-binding contracts: src/types/session-bindings.ts
  • Stats HTTP wire contracts: src/types/stats-wire.ts, src/types/stats-http-contract.ts
  • Compatibility-only barrel: src/types.ts

Ownership Heuristics

  • Runtime wiring or dependency setup: start in src/main/
  • Business logic or service behavior: start in src/core/services/
  • UI interaction or overlay DOM behavior: start in src/renderer/
  • Command parsing or mpv launch flow: start in launcher/
  • Shared contract changes: add or edit the narrowest src/types/<domain>.ts entrypoint; only touch src/types.ts for compatibility exports.
  • User-facing docs: docs-site/
  • Internal process/docs: docs/