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:
ponzischeme89
2026-08-06 22:33:56 +12:00
co-authored by Claude Opus 5
parent 2675e6d82b
commit 4a4df7a73c
257 changed files with 24868 additions and 3108 deletions
+13 -4
View File
@@ -36,7 +36,8 @@ func (s *Server) LoadMaintenance(ctx context.Context) error {
}
s.maintenance.set(state)
if state.Enabled {
s.log.Warn("starting in maintenance mode", "message", state.Message)
s.log.Warn("starting in maintenance mode",
"component", "maintenance", "message", state.Message)
}
return nil
}
@@ -52,7 +53,7 @@ func (s *Server) WatchMaintenance(ctx context.Context, interval time.Duration) {
return
case <-ticker.C:
if err := s.LoadMaintenance(ctx); err != nil {
s.log.Warn("maintenance refresh failed", "error", err)
s.log.Warn("maintenance refresh failed", "component", "maintenance", "error", err)
}
}
}
@@ -91,7 +92,7 @@ func (s *Server) maintenanceGate(next http.Handler) http.Handler {
// being discovered the next time a content request happens to run.
// It also carries informational alerts, because this poll is the one thing the app is
// already listening to — a push channel would be a second connection for a banner.
func (s *Server) handleServiceStatus(w http.ResponseWriter, r *http.Request, _ store.Session) {
func (s *Server) handleServiceStatus(w http.ResponseWriter, r *http.Request, sess store.Session) {
state := s.maintenance.get()
message := state.Message
if state.Enabled && message == "" {
@@ -118,10 +119,18 @@ func (s *Server) handleServiceStatus(w http.ResponseWriter, r *http.Request, _ s
"compatibilityMessage": compatibilityMessage,
"clientVersion": clientVersion(r),
"clientProtocol": clientProtocol(r),
"serverProtocol": membyProtocolVersion,
"serverProtocol": ProtocolVersion,
"featureSchemaVersion": featureSchemaVersion,
"featureRevision": featurePolicy.Revision,
"safeMode": featurePolicy.SafeMode,
"features": featureMap(featurePolicy, clientProtocolNumber(r), clientCapabilities(r)),
// Whether Emby itself is answering, as distinct from whether Memby is. The TV
// direct-plays from Emby, so this is the difference between "the film stopped for
// no reason" and a bar saying what happened and when the next attempt is due.
"emby": embyHealthFor(s.embyHealth.get()),
// One number, not the document: the TV compares it with what it has and only
// fetches /v1/preferences when they differ. That is what turns this poll into the
// delivery channel for an operator pushing someone's settings.
"preferencesRevision": s.preferenceRevisionFor(r, sess),
})
}