mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
206 lines
8.7 KiB
Markdown
206 lines
8.7 KiB
Markdown
# Architecture
|
|
|
|
SubMiner uses a service-oriented Electron architecture with a composition-oriented main process and a modular renderer process.
|
|
|
|
## Goals
|
|
|
|
- Keep behavior stable while reducing coupling.
|
|
- Prefer small, single-purpose units that can be tested in isolation.
|
|
- Keep `main.ts` focused on wiring and state ownership, not implementation detail.
|
|
- Follow Unix-style composability:
|
|
- each service does one job
|
|
- services compose through explicit inputs/outputs
|
|
- orchestration is separate from implementation
|
|
|
|
## Project Structure
|
|
|
|
```text
|
|
src/
|
|
main.ts # Composition root — lifecycle wiring and state ownership
|
|
preload.ts # Electron preload bridge
|
|
types.ts # Shared type definitions
|
|
core/
|
|
services/ # ~55 focused service modules (see below)
|
|
utils/ # Pure helpers and coercion/config utilities
|
|
cli/ # CLI parsing and help output
|
|
config/ # Config schema, defaults, validation, template generation
|
|
renderer/ # Overlay renderer (modularized UI/runtime)
|
|
window-trackers/ # Backend-specific tracker implementations (Hyprland, Sway, X11, macOS)
|
|
jimaku/ # Jimaku API integration helpers
|
|
subsync/ # Subtitle sync (alass/ffsubsync) helpers
|
|
subtitle/ # Subtitle processing utilities
|
|
tokenizers/ # Tokenizer implementations
|
|
token-mergers/ # Token merge strategies
|
|
translators/ # AI translation providers
|
|
```
|
|
|
|
### Service Layer (`src/core/services/`)
|
|
|
|
- **Startup** — `startup-service`, `app-lifecycle-service`
|
|
- **Overlay** — `overlay-manager-service`, `overlay-window-service`, `overlay-visibility-service`, `overlay-bridge-service`, `overlay-runtime-init-service`
|
|
- **Shortcuts** — `shortcut-service`, `overlay-shortcut-service`, `overlay-shortcut-handler`, `shortcut-fallback-service`, `numeric-shortcut-service`
|
|
- **MPV** — `mpv-service`, `mpv-control-service`, `mpv-render-metrics-service`
|
|
- **IPC** — `ipc-service`, `ipc-command-service`, `runtime-options-ipc-service`
|
|
- **Mining** — `mining-service`, `field-grouping-service`, `field-grouping-overlay-service`, `anki-jimaku-service`, `anki-jimaku-ipc-service`
|
|
- **Subtitles** — `subtitle-ws-service`, `subtitle-position-service`, `secondary-subtitle-service`, `tokenizer-service`
|
|
- **Integrations** — `jimaku-service`, `subsync-service`, `subsync-runner-service`, `texthooker-service`, `yomitan-extension-loader-service`, `yomitan-settings-service`
|
|
- **Config** — `runtime-config-service`, `cli-command-service`
|
|
|
|
### Renderer Layer (`src/renderer/`)
|
|
|
|
The overlay renderer is split by concern so `renderer.ts` stays focused on bootstrapping, IPC wiring, and module composition.
|
|
|
|
```text
|
|
src/renderer/
|
|
renderer.ts # Entrypoint/orchestration only
|
|
context.ts # Shared runtime context contract
|
|
state.ts # Centralized renderer mutable state
|
|
subtitle-render.ts # Primary/secondary subtitle rendering + style application
|
|
positioning.ts # Visible/invisible positioning + mpv metrics layout
|
|
handlers/
|
|
keyboard.ts # Keybindings, chord handling, modal key routing
|
|
mouse.ts # Hover/drag behavior, selection + observer wiring
|
|
modals/
|
|
jimaku.ts # Jimaku modal flow
|
|
kiku.ts # Kiku field-grouping modal flow
|
|
runtime-options.ts # Runtime options modal flow
|
|
subsync.ts # Manual subsync modal flow
|
|
utils/
|
|
dom.ts # Required DOM lookups + typed handles
|
|
platform.ts # Layer/platform capability detection
|
|
```
|
|
|
|
## Flow Diagram
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
classDef root fill:#1f2937,stroke:#111827,color:#f9fafb,stroke-width:1.5px;
|
|
classDef orchestration fill:#334155,stroke:#0f172a,color:#e2e8f0;
|
|
classDef domain fill:#1d4ed8,stroke:#1e3a8a,color:#dbeafe;
|
|
classDef boundary fill:#065f46,stroke:#064e3b,color:#d1fae5;
|
|
|
|
subgraph Entry["Entrypoint"]
|
|
Main["src/main.ts\ncomposition root"]
|
|
end
|
|
class Main root;
|
|
|
|
subgraph Boot["Startup Orchestration"]
|
|
Startup["startup-service"]
|
|
Lifecycle["app-lifecycle-service"]
|
|
AppReady["app-ready flow"]
|
|
end
|
|
class Startup,Lifecycle,AppReady orchestration;
|
|
|
|
subgraph Runtime["Runtime Domains"]
|
|
OverlayMgr["overlay-manager-service"]
|
|
OverlayWindow["overlay-window-service"]
|
|
OverlayVisibility["overlay-visibility-service"]
|
|
Ipc["ipc-service\nipc-command-service"]
|
|
RuntimeOpts["runtime-options-ipc-service"]
|
|
Mpv["mpv-service\nmpv-control-service"]
|
|
Subtitle["subtitle-ws-service\nsecondary-subtitle-service"]
|
|
Shortcuts["shortcut-service\noverlay-shortcut-service"]
|
|
end
|
|
class OverlayMgr,OverlayWindow,OverlayVisibility,Ipc,RuntimeOpts,Mpv,Subtitle,Shortcuts domain;
|
|
|
|
subgraph Adapters["External Boundaries"]
|
|
Config["src/config/*"]
|
|
Cli["src/cli/*"]
|
|
Trackers["src/window-trackers/*"]
|
|
Integrations["src/jimaku/*\nsrc/subsync/*"]
|
|
end
|
|
class Config,Cli,Trackers,Integrations boundary;
|
|
|
|
Main -->|bootstraps| Startup
|
|
Main -->|registers lifecycle hooks| Lifecycle
|
|
Lifecycle -->|triggers| AppReady
|
|
|
|
Main -->|wires| OverlayMgr
|
|
Main -->|wires| Ipc
|
|
Main -->|wires| Mpv
|
|
Main -->|wires| Shortcuts
|
|
Main -->|wires| RuntimeOpts
|
|
Main -->|wires| Subtitle
|
|
|
|
Main -->|loads| Config
|
|
Main -->|parses| Cli
|
|
Main -->|delegates backend state| Trackers
|
|
Main -->|calls integrations| Integrations
|
|
|
|
OverlayMgr -->|creates window| OverlayWindow
|
|
OverlayMgr -->|applies visibility policy| OverlayVisibility
|
|
Ipc -->|updates| RuntimeOpts
|
|
Mpv -->|feeds timing + subtitle context| Subtitle
|
|
Shortcuts -->|drives overlay actions| OverlayMgr
|
|
```
|
|
|
|
## Composition Pattern
|
|
|
|
Most runtime code follows a dependency-injection pattern:
|
|
|
|
1. Define a service interface in `src/core/services/*`.
|
|
2. Keep core logic in pure or side-effect-bounded functions.
|
|
3. Build runtime deps in `main.ts`; extract an adapter/helper only when it adds meaningful behavior or reuse.
|
|
4. Call the service from lifecycle/command wiring points.
|
|
|
|
This keeps side effects explicit and makes behavior easy to unit-test with fakes.
|
|
|
|
## Lifecycle Model
|
|
|
|
- **Startup:**
|
|
- `startup-service` handles initial argv/env/backend setup and decides generate-config flow vs app lifecycle start.
|
|
- `app-lifecycle-service` handles Electron single-instance + lifecycle event registration.
|
|
- App-ready flow performs ready-time initialization (config load, websocket policy, tokenizer/tracker setup, overlay auto-init decisions).
|
|
- **Runtime:**
|
|
- CLI/shortcut/IPC events map to service calls.
|
|
- Overlay and MPV state sync through dedicated services.
|
|
- Runtime options and mining flows are coordinated via service boundaries.
|
|
- **Shutdown:**
|
|
- `app-lifecycle-service` registers cleanup hooks (`will-quit`) while teardown behavior stays delegated to focused services from `main.ts`.
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
classDef phase fill:#334155,stroke:#0f172a,color:#e2e8f0;
|
|
classDef decision fill:#7c2d12,stroke:#431407,color:#ffedd5;
|
|
classDef runtime fill:#0369a1,stroke:#0c4a6e,color:#e0f2fe;
|
|
classDef shutdown fill:#14532d,stroke:#052e16,color:#dcfce7;
|
|
|
|
Args["CLI args / env"] --> Startup["startup-service"]
|
|
class Args,Startup phase;
|
|
|
|
Startup --> Decision{"generate-config?"}
|
|
class Decision decision;
|
|
|
|
Decision -->|yes| WriteConfig["write config + exit"]
|
|
Decision -->|no| Lifecycle["app-lifecycle-service"]
|
|
class WriteConfig,Lifecycle phase;
|
|
|
|
Lifecycle --> Ready["app-ready flow\n(config + websocket policy + tracker/tokenizer init)"]
|
|
class Ready phase;
|
|
|
|
Ready --> RuntimeBus["event loop:\nIPC + shortcuts + mpv events"]
|
|
RuntimeBus --> Overlay["overlay visibility + mining actions"]
|
|
RuntimeBus --> Subtitle["subtitle + secondary-subtitle processing"]
|
|
RuntimeBus --> Subsync["subsync / jimaku integration actions"]
|
|
class RuntimeBus,Overlay,Subtitle,Subsync runtime;
|
|
|
|
RuntimeBus --> WillQuit["Electron will-quit"]
|
|
WillQuit --> Cleanup["service-level teardown\n(unregister hooks, close resources)"]
|
|
class WillQuit,Cleanup shutdown;
|
|
```
|
|
|
|
## Why This Design
|
|
|
|
- **Smaller blast radius:** changing one feature usually touches one service.
|
|
- **Better testability:** most behavior can be tested without Electron windows/mpv.
|
|
- **Better reviewability:** PRs can be scoped to one subsystem.
|
|
- **Backward compatibility:** CLI flags and IPC channels can remain stable while internals evolve.
|
|
|
|
## Extension Rules
|
|
|
|
- Add behavior to an existing service or a new `src/core/services/*` file, not as ad-hoc logic in `main.ts`.
|
|
- Keep service APIs explicit and narrowly scoped.
|
|
- Prefer additive changes that preserve existing CLI flags and IPC channel behavior.
|
|
- Add/update unit tests for each service extraction or behavior change.
|
|
- For cross-cutting changes, extract-first then refactor internals after parity is verified.
|