A dashboard that says "live" has one job: when it says a room is quiet, the room is actually quiet right now. Everything else is decoration. Here is what actually broke while getting that right, in the order it broke.

1. The stale-data trap

The naive implementation keeps the last known value and re-renders it. When the uplink dies, the UI happily keeps showing "still, present" forever. We shipped that for two days and it is genuinely dangerous — it looks like information and it is actually a memory.

The fix is to treat every reading as an event with a max age. Past that age, the state becomes no signal with a "last seen 4 min ago" label. The client does this, not the server, so a frozen tab also degrades honestly instead of freezing on a green card.

2. Clock skew between node and browser

The node has no RTC worth trusting. Early on, a node that booted with a 1970 timestamp made the dashboard claim readings from 56 years in the future, and our "is it fresh?" check passed. We now ignore node-side wall clocks entirely: the server stamps arrival time, and freshness is computed from the server's clock only.

3. Reconnect storms

When the 4G router drops, every client reconnects at once. With SSE this is mostly fine, but we were re-running the whole bootstrap on each reconnect — six requests each — and the box serving the flat started refusing connections. Now: exponential backoff with jitter, and the client keeps its last rendered state on screen through the retry instead of flashing an empty shell.

4. Don't smooth what you don't understand

We had a 30-second moving average on the presence signal, because the raw series is spiky. It made the chart pretty and the product worse: short visits to the kitchen vanished into the average, and a person sitting still for twenty minutes slowly faded to "no one". We dropped the smoothing and changed the thresholds instead. Rule of thumb: smoothing is for display, never for state.

5. Backfill is a lie with good manners

Our first reconnect logic replayed buffered readings so the chart had no gaps. It looked great. It also meant a 40-minute outage rendered as a smooth, plausible curve of data that was collected while the network was gone — and worse, it re-triggered "moved" states for the dashboard's history. Now gaps are drawn as gaps.

6. Render cost is a real constraint on cheap hardware

The client is a low-end TV box browser, not a laptop. A naive re-render of the whole timeline on each event pinned it. We moved to: fixed-size ring buffer, canvas for the sparkline, and text nodes updated only when their value actually changes. That took idle CPU from ~40% to under 5%.

What "live" should mean

If you take one thing: make the absence of data a first-class state, and render it as loudly as presence. Every monitoring product I have seen fail in the field failed this way — not by being wrong, but by being confidently stale.

Live dashboard (a real device in a real apartment, updating about once per second): https://annest.cn/live/

AnNest is a consumer safety display, not a medical device. It does not detect falls, does not measure vitals, and does not call for help. If something is an emergency, call your local emergency number.