Channel
channel.ts wraps the browser’s BroadcastChannel API. All cross-role messaging goes through it.
Message shape
Section titled “Message shape”export type ChannelMessage = | { type: 'show'; target: ScreenId; itemId: ItemId } | { type: 'clear'; target: ScreenId }Every message names a single target screen. Multiple screens can be addressed only by sending multiple messages (e.g. the simulator’s Reset iterates over Object.keys(settings.screens)).
import { createChannel } from '../channel'
const channel = createChannel()
// Send (controller)channel.send({ type: 'show', target: 'screen-1', itemId: 'a' })
// Receive (screen)const off = channel.onMessage((msg) => { if (msg.target !== myId) return if (msg.type === 'show') setItem(getItem(msg.itemId)) if (msg.type === 'clear') setItem(null)})
// Clean upoff()channel.close()- Same origin, same machine. All open browser contexts (tabs, windows, iframes) on the same origin receive each other’s messages.
- The sender does not receive its own message. Senders update their local state directly.
Multi-machine path
Section titled “Multi-machine path”For walls spread across machines, the same API can be backed by a tiny WebSocket relay (Node + ws, ~50 lines). The shape of createChannel() stays the same; only the implementation changes. Call sites don’t move.