stale-page-not-broken-server¶
"The GUI isn't rendering" had five suspects; a private browser tab acquitted four of them in one experiment — the phone was faithfully rendering a corpse it had cached weeks earlier.
The debugging arc (2026-07-08)¶
Report: "GUI not rendering" on phone Safari over Tailscale. Suspects: server down, code broken, Tailscale routing, page bug, phone-side state. Evidence gathered in order:
/healthzreturned ok with the right sha → server up, right checkout.- Headless Chromium loaded the exact tailnet URL the phone uses: page rendered, zero console errors, all requests 200 → not the code, not the route.
- That leaves phone-side state. Discriminating experiment: open the same URL in a private Safari tab (same phone, same network, no cache). Verdict from the maintainer: "It works."
Root cause: Safari had cached a dead page — saved while a defunct static server briefly occupied the bookmark's port — and heuristic caching kept serving it. Every layer below the browser was healthy.
The fixes, one per layer¶
- Never let it recur: the launcher HTML is served with
Cache-Control: no-store(_engine/server.py), pinned by a test — the page changes every slice, so no copy of it should ever be cacheable. - Make it diagnosable from the phone:
/healthzreports host, git sha, uptime, keeper heartbeat, provider availability, and the page footer renders it. "Is it down, or is it me?" is now answerable without a desktop.
The transferable lesson¶
When a client reports breakage that the server-side evidence contradicts, the cheapest discriminating experiment is a cache-free client (private tab, curl, different device) — it splits the hypothesis space into "server/network" vs "client state" in one move. And any page that changes on every deploy should say what it is (sha in the footer) so staleness is visible instead of invisible.