Skip to content

Architecture

Each role (controller, screen, simulator, launcher) is a separate HTML entry declared in vite.config.ts. There is no runtime router.

build: {
rollupOptions: {
input: {
launcher: resolve(__dirname, 'index.html'),
controller: resolve(__dirname, 'controller.html'),
screen: resolve(__dirname, 'screen.html'),
simulator: resolve(__dirname, 'simulator.html'),
},
},
}

Why:

  • Production display bundles never carry simulator code, so each screen role boots fast with the smallest possible payload.
  • True process isolation between roles. A bug in the controller can’t break the screens.
  • Simpler mental model than a router for what is essentially N independent SPAs sharing source code.

A router gets added per-app only if/when an app grows nested in-page navigation.

The simulator renders the controller and every screen as <iframe> elements at their wall positions. Each iframe loads its own HTML entry (e.g. /screen.html?id=screen-1), runs in its own document, and uses BroadcastChannel to talk to the others.

Why iframes:

  • Each “screen” really is its own document — same as production, just smaller and on the same machine.
  • The simulator faithfully represents the multi-window deployment with no simulator-only code paths in the controller/screen.
  • pointer-events: none on iframes during edit mode lets the simulator capture drag gestures while keeping iframe content inert.

The controller and screens communicate via the browser’s BroadcastChannel API. Zero infra — works across tabs, windows, and iframes on the same origin and same machine.

Limit: same-machine only. When the wall is spread across multiple physical machines, the same API can be backed by a thin WebSocket relay without changing call sites.

Settings as a runtime store, not just a file

Section titled “Settings as a runtime store, not just a file”

config/settings.ts exposes types and defaultSettings. The actual live values are held in settings-store.ts, backed by localStorage and synced across windows via a separate BroadcastChannel.

The settings file is the schema and defaults; the store is the state.