0.1.52 Gateway

This commit is contained in:
ponzischeme89
2026-08-17 19:09:17 +12:00
parent d5a8d3ad88
commit 1da91e40a1
46 changed files with 1626 additions and 106 deletions
+47
View File
@@ -743,6 +743,53 @@ each page read as a pile of unrelated controls. Things to preserve:
the page that can do something about it. A screen that both summarises and changes state
is where an accidental click lives.
**The gateway's own settings are `/admin/settings`**, reached from the account menu in the
top bar rather than from the rail — every other page decides what the *televisions* do, and
this one is about the server process. It is `store.GatewaySettings` (one `app_settings` row)
over `internal/api/gateway_settings.go`, and it holds the household timezone, the log level,
the idle sign-out, the two alert windows and the Emby health probe. Things to preserve:
- **Every field is an override, and `.env` is still the configuration.** Blank means
"whatever this container was started with", which the page prints beside each field, so
clearing a setting is a real undo rather than a value the operator has to remember. A
setting that can legitimately be *off* therefore needs a third value: **-1 is off, 0 is
deployed** (`store.GatewaySettingsOff`), because a plain zero would make "turn this off"
indistinguishable from "leave it alone".
- **Nothing reads the document directly.** The effective-value helpers in
`gateway_settings.go` — `householdLocation`, `sessionIdleExpiry`, `sonarrAlertWindow`,
`radarrAlertWindow`, `embyHealthInterval` — are the only readers, so adding a setting is
one helper beside its config field rather than teaching every call site that an override
exists. `s.cfg.SonarrLocation` in particular should not be read directly any more:
`householdLocation()` is what makes a timezone change reach the schedule rows, the hero
rotation and the sign-in history.
- **A setting that is read once at start-up is not a setting.** `WatchEmbyReachability`
re-reads its cadence every tick and keeps ticking (slowly) while the probe is off, so
switching it back on does not need a restart; the idle sweep reads the expiry inside
`Run` rather than closing over it; and the log level is a `*slog.LevelVar` threaded from
`main` through `Deps.LogLevel`, applied on save rather than waited for — an operator who
has just turned debug on and gone to look at the log must not spend thirty seconds
believing it did not work. `deployedLogLevel` is remembered because clearing the
override has to restore *something*, and the variable itself has by then been moved.
**Two things a television does are notifications now**, both in `internal/api/device_activity.go`.
`TypeDeviceFirstUse` announces the first time a set opened Memby on a household-local day
and `TypeDeviceUpdated` announces one that finished updating itself. Things to preserve:
- **First use is anchored on `/v1/home`**, not on the auth middleware every call passes
through: a set left on overnight polls `/v1/status` every ten seconds, so any-request
would announce it at midnight — an event nobody did, at the hour nobody is reading. It is
marked *before* the cached response is served, because a household whose rows are still
warm from another set has still just been opened by this one.
- **The answer comes from the insert.** `store.MarkDeviceDay` is `ON CONFLICT DO NOTHING`
on `device_activity_days`, so two televisions racing on the same row cannot both be told
they were first. The marks are retired with the device and pruned by their own
housekeeping task.
- **An update needs a previous version to be an update.** `announceDeviceUpdate` fires from
`captureClientIdentity` — the only place a set that updated in place is ever seen, since
it never signs in again — and an empty previous version is a build this gateway had never
been told the version of, not a version that moved. A *downgrade* is still announced: a
sideloaded step backwards is news too.
**Explaining a recommendation** is `recommend/explain.go`: `Why(profile, item, limit)` is a
pure function turning the learned weights into the phrases a detail page shows. It is kept
apart from `Score` on purpose — the scorer decides *order* and may be opaque, this decides