feat: add comprehensive architecture documentation and migration plan for refactor sessions

This commit is contained in:
2026-05-24 15:38:15 +02:00
parent 67f3e60953
commit c168c3b84a
5 changed files with 1334 additions and 0 deletions
+211
View File
@@ -0,0 +1,211 @@
# Architecture Audit
## Scope
This audit documents the current architecture of the Electron wrapper and the refactor boundaries agreed for future work. It is based on the previous architectural analysis and should be treated as the source of truth for refactor sessions.
The project is not being redesigned from scratch. The goal is to preserve the current working model, reduce lifecycle risk, and make Electron startup, NodeCG process management, updates, and security easier to test and maintain.
## Current Mental Model
The application is an Electron wrapper that lives almost entirely in the main process. There is no custom renderer, no preload script, and no meaningful IPC layer.
Electron starts a local NodeCG runtime and loads HTTP dashboards from `localhost` or `127.0.0.1`. The main process owns startup, provisioning, window creation, navigation policy, update checks, and shutdown.
## Existing Strengths
- The project already uses TypeScript strictness and has passing tests.
- NodeCG process handling is encapsulated in a dedicated module.
- Runtime provisioning preserves user-owned directories: `cfg`, `db`, and `logs`.
- Navigation security is separated from window creation.
- Packaging has a clear dependency on `lib/nodecg` as an Electron resource.
- There is no accidental IPC surface.
- Browser security defaults are mostly strong: `nodeIntegration: false`, `contextIsolation: true`, and `sandbox: true`.
## Current Architecture
```text
src/main/
main.ts
nodecg/
process-manager.ts
runtime-provisioner.ts
windows/
window-factory.ts
navigation-security.ts
updates/
update-manager.ts
scripts/
build-scoreko-bundle.mjs
```
## Main Process Responsibilities
`src/main/main.ts` currently performs several roles:
- Configures Electron app metadata and paths.
- Sets `userData`.
- Acquires the single-instance lock.
- Prepares the managed NodeCG runtime.
- Relaunches after first runtime installation.
- Creates loading and main windows.
- Starts NodeCG.
- Waits for HTTP readiness.
- Loads dashboards.
- Schedules updates.
- Handles activation and shutdown.
This is not unmanageable, but it is the main coupling point and should be split carefully.
## NodeCG Runtime
The current runtime strategy should be preserved:
- Install the managed NodeCG runtime under Electron `userData`.
- Preserve user data directories across managed runtime replacement.
- Run NodeCG through the Electron binary using `ELECTRON_RUN_AS_NODE`.
- Wait for the local HTTP endpoint before loading dashboards.
- Stop the process tree on shutdown.
This model is central to the product and should not be replaced during the refactor.
## Lifecycle Risks
### Import-Time Side Effects
`main.ts` performs work at import time, including app configuration, path setup, lock acquisition, URL calculation, and global state initialization.
Risk:
- Startup behavior is harder to test without real Electron.
- Unit tests must work around global mutable state.
- Future lifecycle changes are more likely to produce hidden side effects.
Decision:
- Move side-effect-free calculation into pure functions.
- Keep Electron side effects inside the entrypoint or an explicit bootstrap layer.
### Mixed Orchestration
`main.ts` combines orchestration with concrete runtime, window, updater, delay, and shutdown behavior.
Risk:
- Adding lifecycle features increases coupling.
- Startup and shutdown behavior is hard to cover end to end.
- Error paths become difficult to reason about.
Decision:
- Extract a small `ApplicationController`.
- Give it explicit lifecycle states.
- Keep behavior equivalent before moving folders.
### macOS Activation
The `activate` flow can recreate the main window and load the dashboard without guaranteeing NodeCG is alive and ready.
Risk:
- On macOS, restoring the app after all windows are closed can race with NodeCG readiness.
Decision:
- Route activation through the same readiness-aware controller used by initial startup.
## Process Management Risks
NodeCG process management is generally well isolated, but process tree shutdown is a sensitive boundary.
Current concern:
- Windows shutdown uses `taskkill` with `shell: true`.
- The PID is numeric, so command injection risk is low, but platform process control should be isolated and tested.
Decision:
- Move Windows and POSIX process termination into a platform adapter.
- Keep process manager focused on NodeCG lifecycle.
- Test process-kill command construction separately.
## Updater Risks
The updater is the clearest controlled rewrite candidate.
Current concerns:
- Remote JSON is trusted without strong schema validation.
- Asset URLs need stricter protocol and host validation.
- Installer integrity is not verified.
- Download behavior should use safe temporary paths and atomic finalization.
- User-facing Spanish text contains visible encoding corruption.
Decision:
- Rewrite the updater in small modules.
- Validate remote update metadata before use.
- Validate download URLs.
- Keep dialogs and installation side effects separate from fetch and download logic.
- Fix encoding as part of the updater cleanup.
## Electron Security Audit
Current good defaults:
- `nodeIntegration: false`
- `contextIsolation: true`
- `sandbox: true`
- No preload script
- No exposed IPC bridge
Security gaps to address:
- No explicit permission handler.
- No explicit devtools policy by environment.
- `webSecurity` should be explicitly set.
- Navigation should remain restricted to allowed local origins.
- CSP is constrained by NodeCG, but should be reviewed where feasible.
Decision:
- Keep the browser surface minimal.
- Do not add preload or IPC unless a clear product need appears.
- Harden Electron defaults explicitly even when current defaults already behave safely.
## Script And Packaging Risks
The build scripts depend on the parent repository layout.
Risk:
- CI or external workspaces may fail if the expected sibling project is missing.
Decision:
- Do not introduce a larger build framework.
- Normalize path validation and shared constants.
- Make script assumptions explicit and fail with clear errors.
## Refactor Boundary
Allowed controlled rewrites:
- Updater internals.
- Application lifecycle controller.
- Platform process termination adapter.
- Path and URL calculation helpers.
Not allowed:
- Replacing the Electron plus NodeCG runtime model.
- Adding a renderer architecture without a product requirement.
- Adding IPC only for architectural symmetry.
- Moving folders before behavior is protected by tests.
- Introducing broad framework abstractions.
## Conclusion
The project has a healthier base than a typical Electron wrapper at this stage. The refactor should make the main process boring, explicit, and testable while preserving the current NodeCG runtime model.
The target is not an enterprise architecture. The target is a small Electron shell with clear lifecycle ownership, hardened update and process boundaries, and minimal browser privileges.
+203
View File
@@ -0,0 +1,203 @@
# Architecture Rules
## Purpose
These rules define the constraints for future refactor sessions. They are intentionally practical: every rule should either protect behavior, reduce Electron risk, or keep the codebase easier to test.
## Core Rules
### Preserve The Product Model
- The app remains an Electron main-process wrapper around a local NodeCG runtime.
- NodeCG continues to be launched locally.
- Dashboards continue to load from approved local HTTP origins.
- The managed runtime continues to live under Electron `userData`.
- Runtime provisioning must preserve `cfg`, `db`, and `logs`.
### Avoid Unnecessary Architecture
- Do not add a renderer architecture unless a user-facing feature requires it.
- Do not add a preload script unless desktop APIs must be exposed to web content.
- Do not add IPC for organizational neatness.
- Do not introduce broad frameworks for lifecycle, dependency injection, logging, or configuration.
- Add an abstraction only when it removes real complexity or isolates a risky boundary.
### Keep Behavior Stable
- Preserve current behavior before reorganizing files.
- Write tests around existing behavior before extracting lifecycle code.
- Move files separately from behavior changes.
- Run typecheck, tests, and lint after meaningful refactor steps.
## TypeScript Rules
- Do not use `any`.
- Prefer explicit domain types at module boundaries.
- Validate unknown external input before narrowing.
- Keep pure functions pure.
- Prefer narrow interfaces over large service objects.
- Avoid global mutable state outside bootstrap or explicit controllers.
## Electron Rules
### BrowserWindow Defaults
Every application window must use secure defaults:
```text
nodeIntegration: false
contextIsolation: true
sandbox: true
webSecurity: true
```
Additional rules:
- Devtools availability must be controlled by environment or explicit config.
- Permission requests must be denied by default.
- New-window behavior must be blocked unless explicitly allowed.
- Navigation must be allowlisted.
- Remote content must not gain Node.js access.
### Preload Rules
Current decision:
- No preload script is required.
If a preload becomes necessary:
- Keep it minimal.
- Expose APIs only through `contextBridge`.
- Do not expose raw `ipcRenderer`.
- Do not include business logic in preload.
- Validate all payloads crossing the boundary.
- Treat preload as part of the security boundary, not as a convenience layer.
### Renderer Rules
Current decision:
- There is no custom renderer.
If a renderer is added later:
- It must not assume Node.js access.
- It must communicate through typed, validated IPC only.
- It must not own NodeCG process lifecycle.
- It must not bypass navigation or permission policies.
## IPC Rules
Current decision:
- No IPC layer is needed.
If IPC becomes necessary:
- Define all channel names in one module.
- Use explicit request and response types.
- Validate every payload at runtime.
- Use allowlisted handlers only.
- Never expose filesystem, process, shell, or update primitives directly.
- Never expose raw Electron APIs to web content.
- Keep handlers small and delegate to tested services.
- Return structured errors instead of throwing raw implementation details across IPC.
Example target shape:
```text
src/main/ipc/
channels.ts
register-handlers.ts
validators.ts
src/shared/ipc/
types.ts
```
Do not create this structure until IPC is genuinely needed.
## NodeCG Runtime Rules
- Keep NodeCG process ownership in the main process.
- Launch NodeCG with `ELECTRON_RUN_AS_NODE`.
- Validate the runtime installation before launching it.
- Wait for HTTP readiness before loading dashboards.
- Treat process stdout and stderr as diagnostic information only.
- Stop the full process tree on app shutdown.
- Keep platform process termination behind an adapter.
## Filesystem Rules
- Filesystem behavior must live behind domain modules.
- Runtime provisioning must never delete user-owned `cfg`, `db`, or `logs`.
- Downloads must stay inside safe temporary directories.
- Paths from config, remote metadata, or user-controlled sources must be validated before use.
- Avoid scattering path construction across unrelated modules.
## Update Rules
- Treat remote update metadata as untrusted.
- Validate update JSON with a runtime schema.
- Validate asset URLs before download.
- Prefer `https:` URLs for production updates.
- Fail closed when metadata is malformed.
- Download to a safe temporary file.
- Finalize downloads atomically.
- Keep fetch, validation, download, dialog, and install steps separate.
- Fix user-facing encoding issues when touching updater text.
- Do not execute downloaded installers unless validation has succeeded.
## Navigation Rules
- Allow only expected NodeCG dashboard origins.
- Prefer explicit URL parsing over string prefix checks.
- Block external navigation by default.
- Block unexpected new-window attempts.
- Keep navigation policy testable as pure logic where possible.
## Configuration Rules
- Parse configuration once.
- Keep configuration access centralized.
- Avoid reading environment variables throughout the codebase.
- Keep runtime defaults explicit.
- Ensure build scripts and app runtime agree on shared constants where appropriate.
## Process Rules
- Child process management must sit behind a small interface.
- Platform-specific kill behavior must be isolated.
- Windows process termination must validate numeric PIDs before command construction.
- Shutdown must be idempotent.
- Repeated quit events must not trigger duplicate process cleanup.
## Testing Rules
- Test pure path and URL functions directly.
- Test lifecycle states without launching real Electron where possible.
- Test updater validation with malformed metadata.
- Test navigation allow and block cases.
- Test process shutdown edge cases.
- Add integration-style coverage only where unit tests cannot represent the Electron behavior.
## Refactor Rules
- Do not refactor unrelated modules in the same change.
- Do not change formatting across the repository unless requested.
- Do not move folders and change behavior in the same step.
- Prefer small commits or small reviewable patches.
- Leave existing passing tests intact unless the product behavior intentionally changes.
## Security Baseline
The secure baseline is:
- No Node.js in web content.
- No preload unless needed.
- No IPC unless needed.
- Local navigation only.
- Deny permissions by default.
- Validate remote update data.
- Validate downloaded update assets.
- Keep process and filesystem access in main-process services only.
+310
View File
@@ -0,0 +1,310 @@
# Migration Plan
## Goal
Refactor the Electron main-process architecture without changing product behavior. The plan is sequential and should be followed in order so future sessions can make progress without reinterpreting the architecture.
## Migration Principles
- Preserve behavior before moving structure.
- Add tests around risky lifecycle behavior before refactoring it.
- Keep the NodeCG runtime model intact.
- Keep Electron minimal.
- Avoid introducing IPC, preload, or renderer code unless required by a concrete feature.
- Prefer small pure functions for paths, URLs, update metadata, asset selection, and navigation policy.
- Rewrite only the degraded areas: updater, lifecycle orchestration, and platform process shutdown.
## Phase 1: Freeze Existing Behavior
Purpose:
Protect current startup, shutdown, provisioning, navigation, and update behavior before extracting modules.
Tasks:
- Add tests for first-run runtime provisioning and relaunch behavior.
- Add tests for loading window and main window ordering.
- Add tests for update scheduling behavior.
- Add tests for shutdown when NodeCG is running.
- Add tests for shutdown when NodeCG is already stopped.
- Add tests for double quit or repeated shutdown calls.
- Add tests for macOS-style activation after windows are closed.
Acceptance criteria:
- Existing tests continue to pass.
- New tests describe current behavior, not the desired future behavior.
- No folder reorganization occurs in this phase.
## Phase 2: Extract Pure Path And URL Logic
Purpose:
Remove deterministic calculations from `main.ts` while keeping Electron side effects in the entrypoint.
Target files:
```text
src/main/app/paths.ts
src/main/config/runtime-config.ts
```
Tasks:
- Extract `userData` path calculation.
- Extract managed runtime path calculation.
- Extract NodeCG dashboard URL construction.
- Extract runtime configuration parsing.
- Keep Electron `app.setPath`, `app.setName`, and lock handling in bootstrap code.
Acceptance criteria:
- Extracted functions are pure.
- Tests cover path and URL edge cases.
- No Electron app object is required to test the extracted logic.
## Phase 3: Introduce ApplicationController
Purpose:
Make lifecycle explicit without redesigning the app.
Target file:
```text
src/main/app/application-controller.ts
```
Required states:
```text
idle
preparing
starting
ready
stopping
stopped
failed
```
Responsibilities:
- Prepare runtime.
- Start NodeCG.
- Wait for readiness.
- Create or reuse windows through a window service.
- Load dashboards only after NodeCG readiness.
- Schedule update checks.
- Handle activation safely.
- Handle shutdown idempotently.
Non-responsibilities:
- Direct process-kill command construction.
- Direct update metadata parsing.
- Direct Electron `BrowserWindow` option construction.
- Business logic inside preload or renderer code.
Acceptance criteria:
- `main.ts` becomes a thin bootstrap.
- Activation uses readiness-aware startup behavior.
- Shutdown is idempotent.
- Tests cover state transitions and failure paths.
## Phase 4: Extract Shutdown Service
Purpose:
Make shutdown behavior predictable and easy to test.
Target files:
```text
src/main/app/shutdown-service.ts
src/main/nodecg/nodecg-process-service.ts
```
Tasks:
- Centralize app shutdown sequencing.
- Ensure NodeCG is stopped before app exit completes.
- Handle repeated shutdown requests.
- Handle process already exited.
- Preserve current quit behavior.
Acceptance criteria:
- Repeated quit calls do not duplicate process termination.
- Tests cover `before-quit`, process exit before shutdown, and shutdown failure logging.
## Phase 5: Rewrite Updater In Small Modules
Purpose:
Replace the fragile updater internals while preserving user-visible behavior.
Target files:
```text
src/main/updates/update-service.ts
src/main/updates/update-config.ts
src/main/updates/update-download.ts
src/main/updates/update-schema.ts
```
Tasks:
- Define a runtime schema for update metadata.
- Validate remote JSON before using it.
- Validate update asset URLs.
- Restrict protocols to `https:` unless an explicit local development mode exists.
- Select platform assets through pure functions.
- Download to a safe temporary path.
- Finalize downloads atomically.
- Keep dialog behavior outside fetch and download helpers.
- Correct Spanish encoding issues.
- Add tests for malformed JSON, missing assets, invalid URLs, failed downloads, and cancelled dialogs.
Acceptance criteria:
- Invalid update metadata fails closed.
- Downloaded files cannot escape the intended temporary directory.
- User-facing strings render correctly.
- Existing update behavior is preserved where valid metadata is provided.
## Phase 6: Isolate Platform Process Termination
Purpose:
Keep OS-specific process-kill details outside NodeCG lifecycle logic.
Target file:
```text
src/main/nodecg/platform-process-killer.ts
```
Tasks:
- Implement a small interface for terminating process trees.
- Provide Windows and POSIX implementations.
- Validate that Windows PIDs are numeric before command construction.
- Avoid spreading platform conditionals through the process manager.
- Test command selection and error handling.
Acceptance criteria:
- `taskkill` construction is isolated.
- POSIX process termination is isolated.
- Process manager tests no longer need to know platform command details.
## Phase 7: Harden Electron Window Policy
Purpose:
Make Electron browser security explicit and testable.
Target files:
```text
src/main/windows/window-service.ts
src/main/windows/navigation-policy.ts
```
Tasks:
- Explicitly set `webSecurity: true`.
- Keep `nodeIntegration: false`.
- Keep `contextIsolation: true`.
- Keep `sandbox: true`.
- Add a permission request handler that denies by default.
- Define devtools policy by environment.
- Keep navigation allowlist limited to approved local NodeCG origins.
- Prevent unexpected new-window behavior.
- Review CSP options for NodeCG-hosted content where feasible.
Acceptance criteria:
- BrowserWindow options are covered by tests.
- Permission requests are denied unless explicitly allowed.
- Navigation policy has tests for allowed and blocked origins.
## Phase 8: Normalize Scripts And Shared Constants
Purpose:
Reduce packaging fragility without changing the build system.
Targets:
```text
scripts/
src/main/config/
```
Tasks:
- Make repository layout assumptions explicit.
- Validate required paths before build work starts.
- Share package/runtime constants where reasonable.
- Improve error messages for missing parent project or missing NodeCG runtime.
- Keep scripts simple `.mjs` utilities.
Acceptance criteria:
- CI failures explain missing external dependencies clearly.
- Local build behavior remains unchanged.
- No new framework is introduced.
## Phase 9: Reorganize Folders Last
Purpose:
Move files only after behavior is protected and new ownership is clear.
Target structure:
```text
src/main/
app/
config/
windows/
nodecg/
updates/
logging/
src/shared/
```
Tasks:
- Move files into target folders after tests pass.
- Update imports mechanically.
- Avoid changing logic during moves.
- Run typecheck, tests, and lint after each group of moves.
Acceptance criteria:
- File moves are behavior-neutral.
- Test output remains unchanged.
- Imports reflect the target architecture.
## Required Verification Per Phase
Run the relevant subset while iterating, then run all checks before closing a phase:
```text
npm run typecheck
npm test
npm run lint
```
## Stop Conditions
Stop and reassess if:
- A change requires adding IPC without a product need.
- A refactor changes the NodeCG runtime model.
- Update validation requires a product or release-server decision.
- CSP changes break NodeCG dashboards.
- CI requires external repository layout decisions outside this package.
+190
View File
@@ -0,0 +1,190 @@
# Session Handoff
## Context
This handoff captures the agreed architecture direction for future refactor sessions. Do not restart architectural discovery from zero unless the codebase has changed substantially.
The previous analysis concluded that the codebase is fundamentally healthy. The refactor should be controlled and incremental, focused on lifecycle, updater safety, process shutdown, and explicit Electron security.
## Current Technical State
Known validation from the prior analysis:
```text
npm run typecheck
npm test
npm run lint
```
All passed at that time, with 42 tests passing.
## Source Of Truth Documents
Use these documents in order:
1. `docs/refactor/ARCHITECTURE_AUDIT.md`
2. `docs/refactor/ARCHITECTURE_RULES.md`
3. `docs/refactor/TARGET_ARCHITECTURE.md`
4. `docs/refactor/MIGRATION_PLAN.md`
5. `docs/refactor/SESSION_HANDOFF.md`
## High-Level Project Model
This is an Electron wrapper around a local NodeCG runtime.
Important facts:
- The app lives mostly in Electron main.
- There is no custom renderer.
- There is no preload.
- There is no meaningful IPC.
- Electron starts NodeCG locally.
- Electron loads dashboards from local HTTP origins.
- Runtime provisioning happens under Electron `userData`.
- NodeCG is launched through the Electron binary with `ELECTRON_RUN_AS_NODE`.
Do not treat the absence of IPC or preload as a missing feature. It is currently a desirable security property.
## Preserve These Behaviors
- Runtime stored under `userData`.
- Relaunch after first runtime installation when required.
- Preservation of `cfg`, `db`, and `logs`.
- Use of `ELECTRON_RUN_AS_NODE`.
- Waiting for NodeCG HTTP readiness before dashboard load.
- Existing tests for config, provisioning, navigation, process management, and updates.
- Minimal Electron surface.
- No IPC unless required.
## Main Risks To Address
### Lifecycle
`main.ts` currently mixes:
- App configuration.
- Runtime preparation.
- Window handling.
- NodeCG startup.
- Readiness waiting.
- Update scheduling.
- Shutdown.
Refactor target:
- Introduce `ApplicationController`.
- Add explicit lifecycle states.
- Keep `main.ts` or `bootstrap.ts` thin.
### Activation
Current risk:
- macOS activation can recreate a window and load a dashboard without proving NodeCG readiness.
Refactor target:
- Route activation through the same readiness-aware controller as startup.
### Updater
Current risk:
- Remote JSON and asset URLs need stronger validation.
- Download and install behavior should be separated.
- Spanish text contains visible encoding corruption.
Refactor target:
- Rewrite updater internals in small modules.
- Validate metadata.
- Validate URLs.
- Use safe temporary paths.
- Fix encoding.
### Process Shutdown
Current risk:
- Windows process-tree termination uses `taskkill`.
- It is low risk because the PID is numeric, but platform behavior should be isolated.
Refactor target:
- Add `platform-process-killer.ts`.
- Keep platform-specific commands out of NodeCG lifecycle orchestration.
### Electron Security
Current strengths:
- `nodeIntegration: false`
- `contextIsolation: true`
- `sandbox: true`
- No preload.
- No IPC.
Refactor target:
- Add explicit `webSecurity: true`.
- Add permission denial by default.
- Add devtools policy by environment.
- Keep navigation allowlisted.
## Recommended Next Session
Start with Phase 1 from `MIGRATION_PLAN.md`.
Immediate next work:
1. Add lifecycle tests around current behavior.
2. Cover first-run relaunch behavior.
3. Cover loading window and main window order.
4. Cover update scheduling.
5. Cover shutdown idempotency.
6. Cover activation readiness behavior.
Do not move files before these tests exist.
## Important Constraints
- Do not re-architect around a renderer.
- Do not introduce IPC proactively.
- Do not rewrite the whole project.
- Do not move folders before preserving behavior with tests.
- Do not remove the managed NodeCG runtime strategy.
- Do not delete user-owned runtime directories.
- Do not broaden Electron permissions.
## Preferred Refactor Order
1. Tests around current lifecycle behavior.
2. Pure path and URL extraction.
3. `ApplicationController`.
4. Shutdown service.
5. Updater rewrite.
6. Platform process-killer adapter.
7. Electron security hardening.
8. Script normalization.
9. Folder reorganization.
## Completion Criteria For The Refactor
The refactor is successful when:
- Startup is controlled by a tested application controller.
- Shutdown is idempotent.
- Activation cannot load dashboards before NodeCG readiness.
- Updater metadata and URLs are validated.
- Downloads use safe paths and atomic finalization.
- Process-tree termination is isolated by platform.
- Browser windows declare secure settings explicitly.
- Permission requests are denied by default.
- No unnecessary IPC, preload, or renderer has been introduced.
- Typecheck, tests, and lint pass.
## Reminder For Future Agents
This project does not need to become bigger to become safer.
Keep Electron small. Keep NodeCG ownership explicit. Keep remote update data untrusted. Keep the browser surface boring.
+420
View File
@@ -0,0 +1,420 @@
# Target Architecture
## Objective
The target architecture keeps the application small and explicit. Electron should remain a thin shell that owns desktop lifecycle, launches NodeCG, loads local dashboards, manages updates, and shuts down cleanly.
The target is not a full rewrite. It is a gradual extraction of responsibilities from `main.ts` into testable modules.
## Target Structure
```text
src/main/
app/
bootstrap.ts
application-controller.ts
paths.ts
shutdown-service.ts
config/
runtime-config.ts
windows/
window-service.ts
navigation-policy.ts
nodecg/
runtime-provisioner.ts
nodecg-process-service.ts
platform-process-killer.ts
updates/
update-service.ts
update-config.ts
update-download.ts
update-schema.ts
logging/
logger.ts
shared/
result.ts
src/shared/
types/
config.ts
```
`shared/result.ts` is optional and should be added only if it removes repeated error-handling noise.
`src/shared/types/config.ts` should contain only types that are genuinely shared across process boundaries or packages. It should not become a dumping ground.
## Runtime Flow
```text
Electron entrypoint
-> bootstrap app identity, paths, lock, config
-> create ApplicationController
-> prepare managed NodeCG runtime
-> relaunch if first install requires it
-> create loading window
-> start NodeCG process
-> wait for HTTP readiness
-> create or show main window
-> load NodeCG dashboard URL
-> close loading window
-> schedule update checks
```
Shutdown flow:
```text
quit requested
-> mark controller stopping
-> stop update work if needed
-> stop NodeCG process tree
-> close windows
-> allow app exit
```
Activation flow:
```text
activate requested
-> if ready, create or show main window
-> if not ready, route through readiness-aware startup
-> never load dashboard before NodeCG readiness
```
## Module Responsibilities
### `app/bootstrap.ts`
Owns Electron entrypoint side effects:
- Set app name.
- Set app paths.
- Acquire single-instance lock.
- Register Electron app event handlers.
- Instantiate services.
- Delegate startup and shutdown to `ApplicationController`.
Rules:
- Keep this file thin.
- Do not put business logic here.
- Do not make it responsible for update parsing, process killing, or window option construction.
### `app/application-controller.ts`
Owns high-level lifecycle state.
States:
```text
idle
preparing
starting
ready
stopping
stopped
failed
```
Responsibilities:
- Coordinate runtime preparation.
- Coordinate NodeCG startup and readiness.
- Coordinate loading and main windows.
- Coordinate update scheduling.
- Coordinate activation.
- Coordinate shutdown.
Rules:
- State transitions must be explicit.
- Shutdown must be idempotent.
- Activation must not bypass readiness.
- Controller tests should not require real Electron where avoidable.
### `app/paths.ts`
Owns pure path construction:
- Electron `userData` derived paths.
- Managed runtime path.
- Safe temp locations.
- Any path constants shared by startup and provisioning.
Rules:
- No Electron side effects.
- No filesystem writes.
- Pure functions only.
### `config/runtime-config.ts`
Owns runtime configuration:
- Parse environment variables.
- Parse static config.
- Define defaults.
- Return typed runtime config.
Rules:
- Parse once.
- Validate early.
- Do not read environment variables throughout the application.
### `windows/window-service.ts`
Owns Electron window creation and window lifecycle.
Responsibilities:
- Create loading window.
- Create main window.
- Apply secure `webPreferences`.
- Apply devtools policy.
- Register permission handlers.
- Delegate navigation decisions to `navigation-policy`.
Rules:
- No NodeCG process logic.
- No updater logic.
- No runtime provisioning logic.
### `windows/navigation-policy.ts`
Owns pure navigation decisions.
Responsibilities:
- Allow approved local NodeCG origins.
- Block external navigation.
- Block unexpected new-window attempts.
- Normalize URL parsing.
Rules:
- Prefer `URL` parsing.
- Keep policy testable without Electron.
- Do not use broad string-prefix allow checks as the primary control.
### `nodecg/runtime-provisioner.ts`
Owns managed runtime installation and replacement.
Responsibilities:
- Validate bundled runtime source.
- Install runtime into `userData`.
- Replace managed runtime safely.
- Preserve `cfg`, `db`, and `logs`.
- Report whether relaunch is needed.
Rules:
- User-owned data must not be deleted.
- Runtime replacement must be predictable and test-covered.
### `nodecg/nodecg-process-service.ts`
Owns NodeCG process lifecycle.
Responsibilities:
- Validate runtime before launch.
- Start NodeCG with `ELECTRON_RUN_AS_NODE`.
- Capture process output.
- Wait for HTTP readiness.
- Stop the process.
- Delegate platform-specific process-tree termination.
Rules:
- Do not build platform kill commands here.
- Do not create windows here.
- Do not schedule updates here.
### `nodecg/platform-process-killer.ts`
Owns OS-specific process-tree termination.
Responsibilities:
- Terminate a process tree on Windows.
- Terminate a process tree on POSIX systems.
- Validate process IDs before command construction.
- Normalize process-kill errors.
Rules:
- Keep platform branches here.
- Test command construction.
- Keep inputs narrow and typed.
### `updates/update-service.ts`
Owns update orchestration.
Responsibilities:
- Schedule update checks.
- Fetch update metadata through a helper.
- Validate metadata through schema helpers.
- Select the correct platform asset.
- Ask the user before installing.
- Delegate download work.
- Start installer only after validation and download success.
Rules:
- Do not trust remote metadata.
- Do not mix dialogs with JSON parsing.
- Do not mix installer execution with download streaming.
### `updates/update-config.ts`
Owns update settings:
- Feed URL.
- Current app version.
- Platform selection.
- Development-mode behavior.
Rules:
- Keep production and development behavior explicit.
- Do not silently downgrade security in production.
### `updates/update-download.ts`
Owns download behavior:
- Validate URL protocol.
- Download to a safe temp path.
- Write atomically.
- Return a typed result.
Rules:
- No dialogs.
- No installer execution.
- No remote metadata interpretation.
### `updates/update-schema.ts`
Owns runtime validation:
- Update metadata shape.
- Asset shape.
- Version field presence.
- URL field validity before download selection.
Rules:
- Unknown remote JSON must be validated before use.
- Invalid metadata must fail closed.
### `logging/logger.ts`
Optional thin logging boundary.
Rules:
- Add only if it improves consistency.
- Do not introduce a large logging framework.
- Keep logs useful for startup, process, update, and shutdown diagnostics.
## Electron Decisions
- Keep Electron main as the only privileged process.
- Keep Node.js unavailable to web content.
- Keep custom renderer absent unless a concrete feature requires one.
- Keep preload absent unless a desktop API must cross into web content.
- Treat windows as untrusted web surfaces even when loading local NodeCG dashboards.
Required `BrowserWindow` security posture:
```text
nodeIntegration: false
contextIsolation: true
sandbox: true
webSecurity: true
```
Additional decisions:
- Deny permissions by default.
- Control devtools by environment.
- Block external navigation by default.
- Block unexpected new windows.
- Review CSP options for NodeCG-hosted content, but do not break dashboards to satisfy theoretical policy.
## IPC Decisions
Current target:
- No IPC.
- No preload.
- No exposed desktop API.
Future IPC, if needed:
```text
src/main/ipc/
channels.ts
register-handlers.ts
validators.ts
src/shared/ipc/
types.ts
```
IPC must be:
- Explicitly justified by a product requirement.
- Channel allowlisted.
- Payload validated at runtime.
- Typed at compile time.
- Narrow in capability.
IPC must not expose:
- Raw `ipcRenderer`.
- Filesystem primitives.
- Process primitives.
- Shell execution.
- Update installation primitives.
- Arbitrary NodeCG process controls.
## Security Decisions
Security controls to preserve:
- No Node.js in web content.
- Context isolation enabled.
- Sandbox enabled.
- No IPC surface by default.
- Local navigation only.
Security controls to add:
- Explicit `webSecurity: true`.
- Permission handler that denies by default.
- Explicit devtools policy.
- Strong update metadata validation.
- Strong update asset URL validation.
- Safe temporary download paths.
- Atomic download finalization.
- Platform process-kill isolation.
## What This Architecture Should Feel Like
Future maintainers should be able to answer these questions quickly:
- Where does startup happen?
- Where is NodeCG launched?
- Where is readiness checked?
- Where are windows created?
- Where is navigation allowed or denied?
- Where are updates validated?
- Where is shutdown coordinated?
- Where does platform-specific process killing live?
If a future change makes those answers harder, it is moving against the target architecture.