Commit graph

13 commits

Author SHA1 Message Date
BoulderBadgeDad
53391372c3 video: fix detail reload (music router clobber) + reliably show missing episodes
Reload bug: music's router boots first, rewrites an unknown /video-detail/... URL
to /dashboard, and my init read the already-changed URL (no restore) AND dispatched
open-detail before video-detail.js was listening (empty page + stray back button).
Fix: capture the path at SCRIPT-EVAL time (before music boots) and DEFER the
restore to a macrotask so every DOMContentLoaded handler is registered and music's
initial routing has run — then re-assert the real URL. Reload/deep-link now restore
the exact item.

Missing-episodes bug: the full-episode-list cascade only ran via the lazy refresh,
which was gated on ART being missing — so a show that already had posters/logo
never pulled its episode list (stayed owned-only). Added shows.episodes_synced
(schema v5): the worker sets it after a full cascade; show_detail returns it; the
lazy refresh now triggers when NOT synced, so owned + missing episodes populate.
2026-06-14 22:26:37 -07:00
BoulderBadgeDad
6e526d7745 video detail: deeper TMDB extras — trailer, where-to-watch, more like this
Phase 4: dynamic extras fetched LIVE per view (providers change, so not cached)
via GET /detail/<kind>/<id>/extras → engine.item_extras → TMDB
(videos + watch/providers + similar in one call).
- Trailer: a '▶ Trailer' action that opens an in-app YouTube modal embed (Esc /
  click-away to close).
- Where to Watch: provider logos for the region (JustWatch via TMDB).
- More Like This: a poster row of similar titles linking out to TMDB.
Both movie + show pages; all keyless (same TMDB key).

Seam tests: extras parse (trailer priority, provider/similar shape), item_extras
gating on tmdb_id, route registered, markup hooks. (RT/Metacritic via OMDb needs
its own key — offered separately.)
2026-06-14 21:42:41 -07:00
BoulderBadgeDad
efa0632883 video detail: clearlogo hero (TMDB images — no new key)
Phase 3: the stylized transparent title logo replaces the text title in the
billboard (the big Plex/Netflix 'premium' jump). Sourced from TMDB images
(append_to_response=images, include_image_language=en,null) in the same detail
call — no Fanart key needed.

- schema v4: movies.logo_url / shows.logo_url (idempotent migration).
- TMDB client picks an English logo (then language-neutral, then any); enrichment
  backfills logo_url gap-only; show/movie payloads return 'logo'.
- Billboard shows the logo img (with graceful fallback to the text title on error
  / when absent; title kept visually-hidden for a11y). Lazy on-view refresh now
  also triggers when the logo is missing, so existing libraries fill it in.

Seam tests: English-logo pick, backfill + payload, schema.
2026-06-14 21:36:14 -07:00
BoulderBadgeDad
59c88fa0db video: Movie detail page (Netflix flat layout), movies now clickable
- Movie cards in the library now drill into a movie-detail page (both kinds use
  the same open-detail event / video-side navigation).
- New video-movie-detail subpage reuses the .vd-* hooks; video-detail.js is now
  kind-aware (root() targets the active page by kind, billboard/links/actions
  branch on movie vs show). Flat layout: billboard + a details strip (released /
  runtime / studio / status / critic score / quality) + the shared Cast & Crew row.
- Lazy on-view backfill for movies too: engine.refresh_movie_art re-fetches TMDB
  (cast/genres/backdrop/ratings) when missing, regardless of match status, via
  POST /detail/movie/<id>/refresh-art. movie_match_info added.

Seam tests: movie refresh backfills cast/genres, movie_match_info, route
registered, movie subpage markup, cards clickable for both kinds.
2026-06-14 21:31:51 -07:00
BoulderBadgeDad
b9b3b9eed3 video 'capture everything': cast & crew (people + credits) + cast row UI
Last capture piece, schema v3 (new people + credits tables; CREATE IF NOT EXISTS
migrates existing DBs on restart, no wipe):
- people deduped by tmdb_id; credits link to exactly one movie OR show (separate
  nullable FKs + CHECK, no polymorphic id) with department/job/character/order.
- TMDB client appends credits to the detail call (free) and parses cast (name,
  character, photo, billing order) + headline crew (directors/writers/creators).
- enrichment_apply backfills cast/crew gap-only (never clobbers); show/movie
  detail return cast + crew. Populates on view via the existing lazy refresh-art.
- Cast & Crew section on the detail page: grouped crew line + a horizontal
  cast row with circular TMDB headshots, names, characters (accent hover).

Seam tests: TMDB credit parse (+ job filtering, created_by), backfill + people
dedup across titles, gap-only no-clobber, payload shape.
2026-06-14 21:20:08 -07:00
BoulderBadgeDad
8002f93220 video: include season id in show_detail (fix /poster/season/undefined 404)
show_detail's season payload omitted the season id, so the frontend built
/api/video/poster/season/undefined and 404'd — season posters never showed even
once cached. Add the id; harden seasonArt to fall back to the show poster if id
is ever missing. Test pins that seasons carry an int id.
2026-06-14 21:02:35 -07:00
BoulderBadgeDad
986059626f video: lazy season-art backfill on detail view (fixes matched-show art gap)
Root cause: season posters / episode art backfill happen during a show's TMDB
*match*, but already-matched shows never re-run ('Retry all failed' only resets
not_found/error), so existing libraries never got the art.

Fix (Boulder's idea): fetch-on-view + cache. When a show detail opens and any
season lacks a poster, the page calls POST /detail/show/<id>/refresh-art →
engine.refresh_show_art re-fetches /tv/<id> via the TMDB client and backfills
season posters + episode art gap-only, regardless of match status. Cached, so
it's a one-time cost per show; runs once per view; re-renders when done.

Seam tests: refresh_show_art backfills a MATCHED show's seasons, needs TMDB
configured, show_match_info, route registered.
2026-06-14 20:56:57 -07:00
BoulderBadgeDad
e1e0e29432 video 'capture everything' (phase 1): stills, genres, ratings, tagline
Captures the richer metadata the media server already exposes (schema v2;
idempotent migrations + CREATE IF NOT EXISTS, so an existing DB upgrades on
restart with no wipe):
- movies: tagline, rating (audience), rating_critic; shows: tagline, rating,
  first/last air date; episodes: still_url + rating.
- Genres as a normalised many-to-many (genres + movie_genres/show_genres link
  tables — no comma-blob), deduped, replace-on-upsert.
- Plex (.genres/.tagline/.audienceRating/.rating/.thumb) + Jellyfin (Genres/
  Taglines/CommunityRating/CriticRating/Premiere+EndDate/episode Primary) both
  extract them; episode stills served via /api/video/poster/episode/<id>.
- Detail payloads return genres/tagline/rating/air-dates + per-episode has_still;
  the billboard shows a tagline, ★ score, genre chips, and episode rows render
  REAL stills (no more orange placeholder once scanned).

Seam tests for genre dedup/replace, show+episode capture, episode still ref.
Cast/crew (people + credits) is the next phase.
2026-06-14 17:17:20 -07:00
BoulderBadgeDad
c450fa1f9a video detail: 4 season-nav views + view toggle, real Watchlist, Missing filter
Season selection is now switchable via a view toggle (persisted): poster RAIL
(scrollable season cards w/ coverage), TIMELINE band (segments sized by episode
count, filled by owned), TABS (pills), and the LIST dropdown. All drive the same
selection; episodes fade in on change.

- Watchlist button is now REAL: toggles shows.monitored via POST /api/video/monitor
  (set_monitored), reflects 'In Watchlist' state. show_detail returns monitored.
- 'Get Missing' + a 'Missing only' toolbar toggle filter the episode list to
  unowned episodes (actual downloading is the future acquisition subsystem).

Seam tests for the monitor endpoint + bad-input guards; shell hooks updated.
2026-06-14 17:05:51 -07:00
BoulderBadgeDad
20f1214b68 video detail: reuse the artist-hero button + badge styles
- Action buttons now reuse the exact artist-detail classes
  (.library-artist-watchlist-btn + .discog-download-btn/.discog-btn-compact with
  shimmer) instead of bespoke vd-btn styling — identical to the music hero. Wired
  by data-attr (music binds those by id, so no hijack).
- IMDb/TMDB/TVDB source links now render as .artist-hero-badge chips (logo + short
  text fallback, TVDB inverted), matching the artist hero's #artist-hero-badges
  treatment instead of the off-standard text tags.
2026-06-14 16:47:18 -07:00
BoulderBadgeDad
81f2127e71 video: redesign TV-detail as a Netflix billboard (break from the music layout)
Per feedback, this drops the Spotify/artist-page parallel entirely and goes
Netflix:
- Full-bleed billboard (edge-to-edge — breaks out of the host's 40px padding),
  big backdrop with Ken-Burns drift + layered scrims, oversized title, a Netflix
  meta row (owned% · year · rating · seasons · runtime · status), 3-line synopsis,
  and action buttons (Watchlist / Get Missing / external links).
- Per-show accent colour sampled from the poster (canvas) → drives the primary
  button glow, status, episode hover — the SoulSync 'vibe', per title.
- Custom season dropdown + rich episode rows (index, 16:9 thumb w/ hover play,
  title · runtime, 2-line synopsis, Owned/Missing) that fade/stagger in on season
  change.
- Backdrop falls back to a cover-cropped poster; episode stills + genres + cast
  arrive with the 'capture everything' phase. Watchlist/Get-Missing are visual
  pending their endpoints. Shell tests updated.
2026-06-14 16:38:24 -07:00
BoulderBadgeDad
519685fc32 video: rework TV-detail to match the artist-detail vibe + real season art
Addresses the 'feels basic' feedback:
- Hero is now a contained glass card with the backdrop blurred INSIDE it +
  gradient overlay (same treatment as the music artist hero) — no more bare
  gaps around the top/sides. Bigger poster, accent external-link chips
  (IMDb/TMDB/TVDB), refined badges + stat tiles.
- Seasons are a poster-art card grid (season = album) with coverage rings/bars
  and hover-lift, selecting one renders its episodes below (episode = track) —
  episode overviews now shown. Mirrors the artist album-grid -> tracklist.
- Scan now captures real per-season posters (Plex sh.seasons() thumbs / Jellyfin
  /Seasons Primary), served via get_art_ref('season') + /api/video/poster/season.
  Falls back to the show poster until a re-scan populates them.

Seam tests for the season art ref; shell markup tests still green.
2026-06-14 16:13:30 -07:00
BoulderBadgeDad
5a2113bd03 video: TV-show detail page (hero + season/episode tree), isolated
Drill-in from a show card: full-bleed backdrop + poster + title/badges/overview +
stat tiles, then seasons->episodes as collapsible accordions with owned/missing
state and per-season coverage bars (season = album, episode = track — inspired by
the music artist page, premium vibe).

- video-detail.js (isolated IIFE) renders from /api/video/detail/show/<id>;
  backdrop/poster via the proxy.
- Show cards dispatch soulsync:video-open-detail; video-side.js navigates to the
  (non-nav) detail subpage; back button reuses data-video-goto.
- Movies stay non-clickable until the movie-detail page lands next.
- .vd-* CSS scoped to the detail page; music untouched. Shell + isolation tests.
2026-06-14 15:59:29 -07:00