App v0.2.26 and gateway 0.1.20
Client: seek controls, Bazarr subtitle download and cast panel in the player; MDBList ratings strip; episode and schedule detail pages; series pace estimate; what's new panel; install-permission onboarding step; synced per-profile preferences; Emby outage banner. Gateway: rebuilt admin console (one fragment per page), preference history and restore, merged Continue Watching, Emby health probe, subtitle selection and Bazarr download, structured request logging with per-request identity, and embedded build version. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2675e6d82b
commit
4a4df7a73c
+59
-15
@@ -57,14 +57,37 @@ It deploys the **local working tree**, including uncommitted server changes.
|
||||
|
||||
## Logs
|
||||
|
||||
The server writes one compact, structured text line per event, designed for
|
||||
`docker compose logs -f server`. At the default `MEMBY_LOG_LEVEL=INFO`, successful
|
||||
health checks, live maintenance polls and artwork requests are hidden; warnings and
|
||||
failures from those routes are still shown.
|
||||
The server writes one aligned line per event, designed for
|
||||
`docker compose logs -f server` and for the admin page's live log:
|
||||
|
||||
Set `MEMBY_LOG_LEVEL=DEBUG` temporarily and recreate the server container when those
|
||||
high-frequency requests are useful during diagnosis. Library imports log their start,
|
||||
progress after each 500-item page, and final counts and duration.
|
||||
```
|
||||
2026-08-03 11:33:40 INFO playback requested component=playback user=matt device="Living room" client=0.1.60 title="Severance – Good News About Hell" item=184223 type=Episode play_method=DirectStream resume=12m0s runtime=57m0s version=0.1.0
|
||||
2026-08-03 11:33:40 INFO request component=home user=matt device="Living room" client=0.1.60 protocol=1 method=GET path=/v1/home status=200 duration=412ms cache=miss version=0.1.0
|
||||
```
|
||||
|
||||
The timestamp is a column rather than a `time=` field, and the fields are always in the
|
||||
same order: **who and where** first (`component`, `user`, `device`, `client`), then what
|
||||
the event is about, with `version` (the gateway build) and `error` last. `MEMBY_LOG_FORMAT`
|
||||
switches the whole stream to `logfmt` (slog's own text) or `json` for a collector.
|
||||
|
||||
Every line from a request carries the viewer, the television, the app build and the
|
||||
`component` — the part of the app the call came from, derived from the route, so it is
|
||||
right even for an APK too old to report anything about itself.
|
||||
|
||||
Beyond the per-request line, these are logged as events in their own right: sign-in,
|
||||
sign-out and rejected sign-ins; a device removed or renamed from another TV; what
|
||||
somebody asked to play and what happened to it (`playback requested`, `playback
|
||||
started`, `playback stopped` with how much was watched, `next episode resolved`); an
|
||||
update offered or forced; a media request; maintenance and feature-policy changes; and
|
||||
library imports (start, progress per 500-item page, final counts and duration).
|
||||
|
||||
At the default `MEMBY_LOG_LEVEL=INFO`, successful health checks, live maintenance polls,
|
||||
artwork requests, search terms and ten-second playback progress reports are hidden;
|
||||
warnings and failures from those routes are still shown. Set `MEMBY_LOG_LEVEL=DEBUG`
|
||||
temporarily and recreate the server container when they are useful during diagnosis.
|
||||
|
||||
The build that wrote a line is `internal/buildinfo/VERSION`, embedded at compile time —
|
||||
bump it with a meaningful server change. It is also on `/healthz` and in the admin rail.
|
||||
|
||||
## API
|
||||
|
||||
@@ -112,7 +135,6 @@ three fixed rows repeated flat for the client's offline cache:
|
||||
{
|
||||
"rows": [
|
||||
{"id": "continue", "title": "Continue Watching", "kind": "continue", "items": [...]},
|
||||
{"id": "next-up", "title": "Next Up", "kind": "nextup", "items": [...]},
|
||||
{"id": "for-you:pick-up", "title": "Pick this show up again", "kind": "for-you", "items": [...]},
|
||||
{"id": "sonarr-airing-today", "title": "Shows airing in the next 5 days", "kind": "schedule", "items": [...]},
|
||||
{"id": "favorites", "title": "Favourites", "kind": "favorites", "items": [...]},
|
||||
@@ -130,6 +152,11 @@ three fixed rows repeated flat for the client's offline cache:
|
||||
|
||||
The TV renders whatever arrives, so a new row ships without an app release. `kind` picks
|
||||
the card shape; an unrecognised kind falls back to poster cards rather than being dropped.
|
||||
|
||||
There is no Next Up row: those episodes are interleaved into Continue Watching, most
|
||||
recently watched first, so the episode after one that just finished is the first card
|
||||
rather than a card in another row. The response still carries a flat `nextUp` array for
|
||||
televisions running a build that predates the merge.
|
||||
The user's own section toggles still hide the three fixed rows, but never rows the server
|
||||
invented — nobody opted out of a row that did not exist when they last opened Settings.
|
||||
|
||||
@@ -186,6 +213,14 @@ stable `(serverId, sessionId)` key. Incremental passes run every five minutes an
|
||||
after two unchanged pages (at most ten); a daily full pass catches late or out-of-order
|
||||
updates and reconciles deletions. Imports are idempotent.
|
||||
|
||||
Whether a pass is owed is decided by the timestamps recorded in `app_settings`, not by
|
||||
how long this process has been up: the scheduler ticks, asks, and usually does nothing.
|
||||
That is what makes the cadence survive a restart. Driving it from tickers alone meant a
|
||||
redeploy or a bounced container started the interval again and imported immediately, so a
|
||||
gateway that restarted often imported far more often than configured — and, in the other
|
||||
direction, one restarted daily never reached its 24-hour full reconciliation at all. The
|
||||
startup pass follows the same rule; `/admin` still forces either kind on demand.
|
||||
|
||||
Postgres retains the compact source sessions, one derived profile per enabled Emby user,
|
||||
and at most 750 ranked Movie/Series candidates per user. The background builder reads Tracearr's
|
||||
public users endpoint and exact-matches usernames against Emby's user list. Unmatched
|
||||
@@ -204,7 +239,7 @@ The endpoint remains one indexed PostgreSQL read plus JSON enrichment. Tracearr
|
||||
stay frequent but do not rebuild pools. A session's first transition to stopped/completed
|
||||
dirties only its matched user, and all pools rebuild once daily at the configured local
|
||||
off-peak hour. A stored algorithm version triggers a one-time startup migration only when
|
||||
ranking behavior changes. A cold or failed rebuild uses the original live Tracearr/Emby
|
||||
ranking behaviour changes. A cold or failed rebuild uses the original live Tracearr/Emby
|
||||
path, so persistence cannot blank the area.
|
||||
|
||||
Every returned item is enriched with `MembyRecommendationReason` and
|
||||
@@ -293,6 +328,14 @@ and the current 30-minute Emby-verified session. The old admin cookie therefore
|
||||
bypass the gate after the browser session expires. Scripts may continue to use
|
||||
`Authorization: Bearer <MEMBY_ADMIN_TOKEN>` without a browser session.
|
||||
|
||||
That 30 minutes is idle time, not a hard limit: opening an admin page, making any change,
|
||||
or reading one while interacting with it slides the expiry forward once it is inside the
|
||||
last fifteen minutes. What deliberately does **not** extend it is the page's own status
|
||||
poll — a console left open on a second monitor still times out, which is the whole point
|
||||
of the TTL. The page marks its own requests with `X-Memby-Admin-Active` when there has
|
||||
been interaction in the last five minutes, and on a 401 it reloads, so an expiry lands as
|
||||
the sign-in form with `next` pointing back at the page rather than as an error banner.
|
||||
|
||||
| Method | Path | Purpose |
|
||||
| --- | --- | --- |
|
||||
| GET | `/admin/` | The page |
|
||||
@@ -325,9 +368,10 @@ The **Movie ratings** admin page enables MDBList, stores its API key only in the
|
||||
by default. Movie detail pages request `/v1/items/{id}/ratings` independently of essential
|
||||
Emby metadata; missing provider IDs, MDBList errors, invalid or unavailable values all
|
||||
produce an empty successful response. Successful MDBList responses are cached in Redis for
|
||||
24 hours before the configured source filter is applied, so changing visible sources does
|
||||
not consume more MDBList quota. The API key is never returned by either the TV or admin
|
||||
status APIs.
|
||||
24 hours and persisted in Postgres for seven days before the configured source filter is
|
||||
applied, so restarts, multiple TVs, and changing visible sources do not consume more
|
||||
MDBList quota. If MDBList is temporarily unavailable, the most recently persisted response
|
||||
is served stale. The API key is never returned by either the TV or admin status APIs.
|
||||
|
||||
## Library import
|
||||
|
||||
@@ -544,14 +588,14 @@ can review and revoke signed-in TVs from the app's Settings screen.
|
||||
| `MEMBY_GOMEMLIMIT` | `384MiB` | Compose value passed to Go as `GOMEMLIMIT` |
|
||||
| `MEMBY_SERVER_MEMORY_LIMIT` | `512m` | Compose hard memory ceiling for the server container |
|
||||
| `MEMBY_TIMEZONE` | `Pacific/Auckland` | Local day and time labels for schedule rows |
|
||||
| `MEMBY_CLIENT_NAME` | `Memby` | Shown in Emby's device list |
|
||||
| `MEMBY_CLIENT_NAME` | `MbyATV` | Client name sent to Emby; must match the app's direct path |
|
||||
| `MEMBY_HOME_TTL` | `60s` | Also `MEMBY_ITEM_TTL`, `MEMBY_SEARCH_TTL`, `MEMBY_SCREENSAVER_TTL` |
|
||||
| `MEMBY_RECOMMEND_TTL` | `24h` | How long computed recommendation rows stay warm |
|
||||
| `MEMBY_RECOMMEND_TIMEOUT` | `60s` | Bounds a background rebuild |
|
||||
| `MEMBY_TRACEARR_URL` / `MEMBY_TRACEARR_API_KEY` | *empty* | Read-only Tracearr public API; both values are required |
|
||||
| `MEMBY_TRACEARR_SERVER_ID` | *empty* | Optional server scope when Tracearr monitors several servers |
|
||||
| `MEMBY_TRACEARR_SYNC_INTERVAL` | `5m` | Incremental session import cadence; `0` disables scheduling |
|
||||
| `MEMBY_TRACEARR_FULL_INTERVAL` | `24h` | Full reconciliation cadence |
|
||||
| `MEMBY_TRACEARR_SYNC_INTERVAL` | `5m` | Minimum age of the last incremental import before another is owed; `0` disables incremental passes |
|
||||
| `MEMBY_TRACEARR_FULL_INTERVAL` | `24h` | Minimum age of the last full reconciliation before another is owed; `0` disables full passes |
|
||||
| `MEMBY_FOR_YOU_MIN_REBUILD_AGE` | `24h` | Safety bound for non-forced pool rebuilds |
|
||||
| `MEMBY_FOR_YOU_REFRESH_INTERVAL` | `24h` | Acceptable prepared-pool staleness |
|
||||
| `MEMBY_FOR_YOU_REBUILD_HOUR` | `4` | Local hour (0–23) for the daily prepared rebuild |
|
||||
|
||||
Reference in New Issue
Block a user