Okay, doing this: today a hat can show a full 150-minute recording with no error while the camera delivered zero frames — the timer is just wall-clock and nothing checks frames. Fix: define "recording" as frames actually landing on the card, top to bottom, so the app can never show a session as recorded when it's empty.
This might happen: the silent loss stops — a stalled camera fails loud and immediately, so the operator re-records on the spot instead of finding out at decryption. This is the fix for today's incident report.
This might not happen: it's not one switch — it's a few coordinated changes (duration from frames, status gated on real progress, a stall watchdog, a post-session integrity check). Landing only some of them still leaves a hole.
Nothing might happen: on a healthy recording nothing changes — same behavior, just now provably true.
Frame-derived timer + progress-gated status + stall watchdog, then an app post-session check best
App-only check after each session — catches it after the fact, but the device still lies during recording.
Just the stall watchdog — stops runaway empties, but the false timer stays.
Okay, doing this: put the hat's ID in the session name so two hats can never produce the same file name, and don't name or timestamp a session from a clock that hasn't synced yet (these boards have no battery clock and boot with the wrong time until Wi-Fi/NTP corrects it).
This might happen: the duplicate file names across devices stop, so nothing gets overwritten at offload; timestamps become trustworthy.
This might not happen: waiting for the clock adds a few seconds at cold boot before the first record; the hat's ID in the name is what actually guarantees uniqueness, independent of the clock.
Nothing might happen: hats that were already on time and recording alone see no difference.
Hat ID in the name + wait for a synced clock before naming best
Wait for the clock only — what the current PR does; two hats at the same second still collide.
PR #16 — the clock-wait half, open but rotted; needs a rebase and the hat-ID naming added
Okay, doing this: replace vague or missing failures with a specific, plain-language reason and a next step — "camera not detected", "camera stuck in recovery, power-cycle it", "no space left", "camera warming up" — written with the UX doctrine so a field operator knows what to do without calling support.
This might happen: the operator fixes it in the moment (reseat the cable, power-cycle, swap the card) instead of finding a dead session hours later; fewer silent failures walk off site.
This might not happen: it doesn't repair the fault — a genuinely broken camera still can't record, but now the person holding it knows why and what to try.
Nothing might happen: a clean start shows nothing new.
Map each real fault to a doctrine-written operator message + next step best
Keep the generic "couldn't start, turn it off and on" — what mostly exists; hides the actual cause.
Okay, doing this: watch bluetoothd; if it pegs a core and stays there while advertising is up, restart the bluetooth service, and only touch the shared radio if that alone doesn't clear it.
This might happen: a connected phone drops and reconnects; discovery blips for a few seconds; on Zero 3W / A7Z the shared radio can make Wi-Fi hiccup.
This might not happen: recording is not interrupted and no footage is lost.
Nothing might happen: if bluetooth never stalls, it never fires. Pure insurance.
Restart the light service first, escalate to the radio only if needed best
Always bounce the whole radio — simpler, but blips Wi-Fi every time.
Okay, doing this: when a record attempt hits the stale-key block, set the leftover key aside and retry, so the next tap records.
This might happen: the hat records immediately — no power-cycle, no field visit.
This might not happen: nothing is deleted (the key is kept aside, never destroyed); the normal path is untouched.
Nothing might happen: a hat that never lost power mid-record has no stale key, so it never triggers.
Clear it in place, records on the next tap best
Heal only on the next reboot — what the current fix does; still needs a power-cycle.
PR #26 — the next-boot fix, open, ready to test
Okay, doing this: drop the every-5s Wi-Fi check and update the app's connection status from NetworkManager's own change signals instead.
This might happen: the repeated background check disappears; the app still shows live connection status.
This might not happen: no change to the radio (it never scanned) and no change to how it auto-connects.
Nothing might happen: the operator sees the same status as before.
Event-driven from NetworkManager signals best
Just slow the check to ~15s — trivial, keeps the background work.
Read only when asked — simplest, but loses the live "Wi-Fi dropped" update.
Okay, doing this: the auto-enroll already works — on first internet a new hat installs Tailscale, joins with its one-time key, retries until it's on, then wipes the key. The change is to stop copy-pasting those ~150 lines into every board's first-boot script and keep one shared copy.
This might happen: one place to fix enrollment; the four board images stop drifting apart (they already have).
This might not happen: nothing changes in how a new hat actually enrolls — same steps, same behavior.
Nothing might happen: hats already on the tailnet are untouched; this only affects freshly-imaged cards.
One shared enroll unit every board image uses best
Leave it duplicated per board — works today, but drifts and has to be fixed four times.
Okay, doing this: if a hat's update state is stuck on "checking" past a timeout, reset it to idle automatically on the next check, instead of someone having to fix it by hand.
This might happen: a wedged hat can check and update on its own again; no manual intervention.
This might not happen: no change to a healthy update, and the safe A/B install + auto-rollback stays exactly as-is.
Nothing might happen: hats that never wedge never notice. The rest of the OTA is already clean — nothing else to simplify there.
Auto-reset a stale "checking" on the next check best
Keep resetting it by hand — what happens today.
Okay, doing this: ship the live camera preview (see through the hat before and during recording) and the polished Pi 5-line UI into the everyday app, for every hat — not just the Pi 5 test build.
This might happen: the operator sees the camera is actually capturing before committing to a long session — a black preview means no frames, which is exactly the silent-loss failure we just found. Preview becomes the human catch for it.
This might not happen (watch this one): it will not be free — turning preview on in the shipping app re-wakes the "recordings auto-stop a few seconds in" bug, which is dormant today only because production has no preview. The one-line record-start lock has to land first, or this reintroduces the loss it's meant to prevent.
Nothing might happen: on iPhone (preview unsupported today) nothing changes there yet.
Ship the existing preview module everywhere, after the start-lock fix best
Keep preview on the Pi 5 build only — safe, but the fork and the drift stay.
Okay, doing this: keep one set of screens and flows for the whole app; the transport underneath (BLE, direct preview) and the hat type swap behind a capability layer, so the UI never forks per device.
This might happen: design and fix once, and it lands on every hat and phone; the per-line drift (like the four first-boot scripts that already diverged) stops happening.
This might not happen: hardware differences don't vanish — they move behind the capability layer, not into separate screens. And this is the app UI only; the Pi 5 receiver firmware stays its own channel as before.
Nothing might happen: the operator sees the same app no matter which hat — ideally they never know which adapter is underneath.
One UI, device differences behind a capability layer best
Keep the live-preview build as a separate fork — what we have now; it drifts.
Lowest common denominator — drop device-specific features to force sameness; loses capability.