Architecture
Multi-page Vite, no router
Section titled “Multi-page Vite, no router”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.
Iframes in the simulator
Section titled “Iframes in the simulator”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: noneon iframes during edit mode lets the simulator capture drag gestures while keeping iframe content inert.
BroadcastChannel for cross-window comms
Section titled “BroadcastChannel for cross-window comms”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.