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
+38 -6
View File
@@ -95,6 +95,38 @@ func TestRecommendationOnboardingCandidatesMixMoviesAndSeries(t *testing.T) {
}
}
func TestRecommendationOnboardingPeopleSeparatesPerformersAndDirectors(t *testing.T) {
items := []recommend.Item{{
ID: "arrival", Type: "Movie", CommunityRating: 8.2,
People: []recommend.Person{
{ID: "amy", Name: "Amy Adams", Type: "Actor", PrimaryImageTag: "amy-tag"},
{ID: "jeremy", Name: "Jeremy Renner", Type: "Actor"},
{ID: "denis", Name: "Denis Villeneuve", Type: "Director"},
},
}}
actors, actresses, directors := recommendationOnboardingPeople(items, 10)
if len(actors) != 1 || actors[0].Name != "Jeremy Renner" {
t.Fatalf("actors = %#v", actors)
}
if len(actresses) != 1 || actresses[0].Name != "Amy Adams" || actresses[0].ImageTag != "amy-tag" {
t.Fatalf("actresses = %#v", actresses)
}
if len(directors) != 1 || directors[0].Name != "Denis Villeneuve" {
t.Fatalf("directors = %#v", directors)
}
}
func TestRecommendationOnboardingResponseIncludesServerPromptState(t *testing.T) {
raw, err := json.Marshal(recommendationOnboardingResponse{Prompted: true})
if err != nil {
t.Fatal(err)
}
if !strings.Contains(string(raw), `"prompted":true`) {
t.Fatalf("response = %s", raw)
}
}
func TestEpisodeCodeFormatsPlaybackMetadata(t *testing.T) {
item := emby.Summary{Type: "Episode", ParentIndexNumber: 2, IndexNumber: 4}
if got := episodeCode(item); got != "S02E04" {
@@ -253,11 +285,11 @@ func TestBaseRowsShape(t *testing.T) {
Favorites: []json.RawMessage{json.RawMessage(`{"Id":"2"}`)},
})
if len(rows) != 4 {
t.Fatalf("expected 4 base rows, got %d", len(rows))
if len(rows) != 3 {
t.Fatalf("expected 3 base rows, got %d", len(rows))
}
wantIDs := []string{"continue", "next-up", "favorites", "latest-movies"}
wantKinds := []string{"continue", "nextup", "favorites", "latest"}
wantIDs := []string{"continue", "favorites", "latest-movies"}
wantKinds := []string{"continue", "favorites", "latest"}
for i, row := range rows {
if row.ID != wantIDs[i] {
t.Fatalf("row %d id = %q, want %q", i, row.ID, wantIDs[i])
@@ -271,8 +303,8 @@ func TestBaseRowsShape(t *testing.T) {
}
// The favourites row carries the items the client used to assemble itself.
if len(rows[2].Items) != 1 {
t.Fatalf("favourites row lost its items: %+v", rows[2])
if len(rows[1].Items) != 1 {
t.Fatalf("favourites row lost its items: %+v", rows[1])
}
}