Files
SubMiner/docs/architecture/layering.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

2.0 KiB

Layering Rules

Status: active
Last verified: 2026-05-23
Owner: Kyle Yasuda
Read when: deciding whether a dependency direction is acceptable

Preferred Dependency Flow

  1. src/main.ts
  2. src/main/ composition and runtime adapters
  3. src/core/services/ focused services
  4. src/core/utils/ and other pure helpers

Renderer, launcher, plugin, and stats each keep their own local layering and should not become a grab bag for unrelated cross-runtime behavior.

Rules

  • Keep src/main.ts thin; wire, do not implement.
  • Pass deps straight to a handler factory (createXHandler({...})). Do not add 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.
  • Keep renderer concerns in src/renderer/; avoid leaking DOM behavior into main-process code.
  • Treat launcher/*.ts and the runtime bootstrap generators as source of truth for launcher behavior. scripts/build-launcher.ts creates dist/launcher; never hand-edit those artifacts.

Smells

  • main.ts grows because logic was not extracted
  • service reaches directly into unrelated runtime state
  • renderer code depends on main-process internals
  • docs-site page becomes the only place internal architecture is explained