A stream overlay may look like a small piece of UI, but it becomes a distributed system as soon as multiple browser sources need to show the same state. Brotator Tracker had to remain readable on stream, respond to remote controls, survive reconnects, and remember progress after a restart.
The Goal
The tracker supports a challenge spanning more than 60 characters. Viewers need to understand three things at a glance: which character was completed, which one is active, and which one comes next.
The overlay itself is deliberately compact. The more important requirement was ensuring that every open instance receives the same progress update immediately, without polling or manual refreshes.
The Core Approach
The server owns the authoritative state. It loads an ordered array from characters.json and derives the active position from progressIndex. Express handles control requests while a WebSocket server broadcasts state snapshots.
wss.on("connection", (socket) => {
clients.push(socket);
socket.send(JSON.stringify({
type: "update",
characters,
progressIndex,
}));
});
Sending the complete state on connection is important. A browser source can start at any point during a stream and immediately render the correct previous, current, and next characters.
The Main Challenge
Real-time updates alone are not enough. Browser sources can disconnect when streaming software reloads a scene, a reverse proxy resets a connection, or the network briefly drops.
The client therefore chooses ws or wss based on the current page protocol and attempts to reconnect two seconds after a closed connection. The server sends periodic ping messages so long-lived connections continue to produce traffic and remain observable.
State changes also need to survive a process restart. After advancing, undoing, clearing, or reordering the challenge, the server writes the updated array back to JSON before broadcasting the new snapshot.
What Worked
A single ordered array turned out to be enough for the complete workflow. Every entry before or at the current index is marked as done, so the server can rebuild all completion flags from one integer.
The browser then renders only the three relevant entries:
progressIndex - 1as the completed characterprogressIndexas the current characterprogressIndex + 1as the next character
Authenticated bearer-token checks protect state-changing progress actions, while CORS is restricted to the deployed tracker origin. The Dockerfile pins the runtime family to Node 20 and exposes the same port used by the HTTP and WebSocket server.
What I Would Improve Next
The next iteration should move credentials into environment variables and separate persistence from route handling. Asynchronous file writes or a small database would avoid blocking the server and provide a clearer path toward multiple independent challenge runs.
I would also replace status text responses with structured JSON, validate all state transitions centrally, and add automated tests for the first and last character boundaries, reconnect snapshots, and reordering behavior.
The project demonstrated that the visible overlay is only the final layer. Reliable state ownership, persistence, and recovery are what make it useful during a live stream.
The Project
The project page contains the feature overview, technical structure, and deployment summary: