The app on a laptop reported an engine that was never meant to be there

Signing in on a second Mac showed "engine not reachable at
http://127.0.0.1:8010" and 0 of 0 cameras, on an account whose shops were
running and recognising people the whole time. Nothing was broken: Live() and
Cameras() read only the engine on loopback, so the app answered as though the
person had never signed in - and camera sync goes through the engine, which is
why the count was zero rather than stale.

Having no engine is a normal state. A shop PC watches cameras; an owner's
laptop, a manager's machine and a second till being set up do not, and all
three are signed in to the same estate. Both methods now fall back to head
office when loopback fails and somebody is signed in. Loopback is still tried
first: a real shop PC must never be shown a minute-old summary when the engine
two milliseconds away has the live one.

Decisions worth keeping:

- Viewing is on the snapshot, not inferred per screen. Three surfaces read it,
  and a screen that computed it separately is how the shops screen once came
  out labelled Working, in green, above "2 of 3 cameras not connecting".
- fraction_below_gate takes the WORST shop, never an average. 0.10 against
  0.73 averages to 0.42 and hides the only shop anyone needs to visit.
- A remote camera is flagged, and Edit, Remove and Check placement are
  withheld. They talk to a camera on a LAN this computer cannot reach, and a
  button that cannot work is worse than one that is absent.
- connected is three states. null is "no shop computer has reported yet" and
  reads as waiting; false is "Not connecting". A bare false sends somebody to
  check cabling on a camera nobody has tried to reach.
- Snapshots are fetched in Go as data: URIs and cached by snapshot_at. A
  webview <img> resolves a relative src against wails:// and cannot send the
  bearer - the problem VisitorImage already solved - and this screen polls
  every 8 seconds at ~90 KB a camera.
- With no engine AND no session, the engine error is still the answer. The
  person is most likely setting this PC up.

The picture is the last snapshot and the banner says so: there is no live
video from here, because the engine's MJPEG stream is on the shop PC's
loopback behind a router with no inbound route. The LiveHub relay head office
uses is the answer to that and is a further step for this client.

Verified against production: five arrivals and two cameras parsed from the
real API. viewing_test.go covers the fallback, the worst-shop rule, the
withheld credentials and that an unchanged snapshot is fetched once across
two polls.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
2026-09-30 16:33:32 +05:30
parent 97a8ecc03a
commit ecc8bbba6f
10 changed files with 548 additions and 39 deletions

View File

@@ -3307,3 +3307,55 @@ Against real Postgres, on the demo tenant:
- HTML, PDF, GIF and empty bodies are all refused as face images: the check is
on the magic bytes, never the `Content-Type` header, because this endpoint
stores what it is handed and serves it back to a browser.
## Viewer mode: the app on a computer that is not watching anything
Signing in on a second Mac showed `engine not reachable at
http://127.0.0.1:8010` and **0 of 0 cameras**, on an account whose shops were
running and recognising people the whole time. Nothing was broken. `App.Live()`
and `App.Cameras()` read **only** `a.local`, so the app answered as though the
person had never signed in — and camera sync goes *through* the engine, which
is why the count was zero rather than merely stale.
That is the wrong model of what this application is. A shop PC watches
cameras; an owner's laptop, a manager's machine, a second till being set up do
not, and all three are signed in to the same estate. **Having no engine is a
normal state, not a failure**, and the app now says what it can see from where
it is standing instead of reporting the absence of something it does not need.
Both methods try loopback first and fall back to head office when it fails and
somebody is signed in. The order matters: a real shop PC must never be shown
head office's minute-old summary when the engine two milliseconds away has the
live one.
- **`Viewing` is on the snapshot, not inferred in the browser.** Three surfaces
read it — the banner, the camera tally, the getting-started panel — and a
screen that computed it separately is how the shops screen once came out
labelled **Working**, in green, directly above *"2 of 3 cameras not
connecting"*. One fact, one place, the same rule as the tray being a client
of `EngineStatus()`.
- **`fraction_below_gate` is the WORST shop, never an average.** 0.10 against
0.73 averages to 0.42 and hides the only shop anyone needs to visit. Same
rule the heartbeat already follows with `worst_site`.
- **A remote camera is flagged `remote: true`, and the screen withholds Edit,
Remove and Check placement.** Those talk to a camera on a LAN this computer
cannot reach, and an Edit button that cannot work is worse than one that is
absent. The tenant response structurally cannot carry `host`, `username` or
`has_password`, so nothing here can invent them either — a test asserts that.
- **`connected` is three states.** `null` is "no shop computer has reported on
this yet" and reads as *waiting*; `false` is *"Not connecting"*. A bare false
sends somebody to check cabling on a camera nobody has tried to reach.
- **The picture is the last snapshot, and it says so.** There is no live video
here: the engine's MJPEG stream is on the shop PC's loopback behind a router
with no inbound route. Head office's `LiveHub` relay is the answer to that
and is a further step for this client; the banner does not imply otherwise.
- **Snapshots are fetched in Go and passed as `data:` URIs, cached by
`snapshot_at`.** A webview `<img>` resolves a relative src against `wails://`
and cannot send the session's bearer — the same problem `VisitorImage`
already solved — and this screen polls every 8 seconds at ~90 KB a camera, so
re-fetching an unchanged frame is megabytes an hour to redraw the same
picture. Keyed on the server's `snapshot_at`, because a new timestamp is the
only thing that means a new photograph.
- **With no engine AND nobody signed in, the engine error is still the answer.**
There is nothing else to show and the person is most likely setting this PC
up; naming head office there points them at a step they have not reached.