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.
This commit is contained in:
sudacode authored and GitHub committed 2026-10-31 19:39:27 -07:00
1 parent a9a4c4f083
commit 2924f04316
23 files changed
+3173 -2010

No files matched your search

+1
View File
@@ -42,6 +42,7 @@ Update checks and startup launcher migration share a serialized update-state sto
- `src/config/` owns config definitions, defaults, loading, and resolution.
- `src/types/` owns shared cross-runtime contracts via domain entrypoints; `src/types.ts` stays a compatibility barrel.
- `src/main/runtime/composers/` owns larger domain compositions.
- `src/main/assemblies/` owns per-domain assemblies that `src/main.ts` builds once (see [Layering](./layering.md)).
- `src/main.ts` call sites invoke configured runtime handlers and runtime-object methods directly;
do not add local pass-through wrappers around them.
+9 -3
View File
@@ -19,21 +19,27 @@ Read when: you need to find the owner module for a behavior or test surface
- 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`.
- 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-*`, `src/main/character-dictionary-runtime.ts`, `src/main/character-dictionary-runtime/`
- 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-*`
- 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
+5
View File
@@ -23,6 +23,11 @@ Renderer, launcher, plugin, and stats each keep their own local layering and sho
deps builders (`createBuild*MainDepsHandler`, `*-main-deps.ts`) that re-wrap deps.
Adaptation logic (defaults, appState getters/setters, shared state) belongs in the
handler or in a named adapter that returns the deps directly.
- Group a domain's runtimes in one assembly under `src/main/assemblies/` (for example
`createYoutubeAssembly(deps)`). It owns the domain's state, takes an explicit deps
object, and returns its runtimes; `main.ts` builds it once and calls through the result.
Siblings built later in `main.ts` are passed as getters or callbacks, and construction must
not call them.
- Prefer injecting dependencies from `src/main/` instead of reaching outward from core services.
- Keep side effects explicit and close to composition boundaries.
- Put reusable business logic in focused services, not in top-level lifecycle files.