Commit graph

12 commits

Author SHA1 Message Date
BoulderBadgeDad
e2a8d3339a video routing: re-assert the detail URL against late/async music redirects
The reload restore could still be clobbered if music's router redirects on a later
tick. Re-assert the /video-detail URL at 120/350/700ms after restore, but ONLY
while the detail subpage is still showing, so it survives an async redirect without
ever fighting genuine navigation away.
2026-06-14 22:27:42 -07:00
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
0598f5fbda video: real-link routing for detail pages (parity with music artist-detail)
Mirrors the music side instead of a custom scheme: library cards are genuine
<a href='/video-detail/<source>/<kind>/<id>'> links, so reload / new-tab / Back /
Forward all work. Left-clicks are intercepted into SPA nav + history.pushState;
modifier-clicks fall through to the real URL.

- popstate restores the detail from the URL; the '← Library' back button is real
  history.back(); deep-link / reload to a /video-detail/... URL is restored on load
  (path captured before applySide can clear it). Server already serves the SPA for
  these paths (permissive catch-all) — no backend change.
- The path carries a SOURCE segment ('library' = a video.db id today; 'tmdb' /
  search results not yet in the library come later) — your library-vs-search split.
- Coexists with music's pathname router (only touches /video-detail/* and its own
  popstate); music's link/popstate handlers ignore these paths.

Tests: real-link cards, pushState/popstate routing, source segment.
2026-06-14 22:13:51 -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
BoulderBadgeDad
0d86d84307 video side: Settings shows the real music settings page (identically)
Video Settings was a 'coming soon' placeholder. Now it reuses the actual
#settings-page, shown identically for now (no hiding of music-only bits yet) —
the foundation for adding the Movies/TV library mapping next.
- video-side.js: SHARED_PAGES maps video-settings -> the music 'settings' page;
  showPage sets body[data-video-page] and triggers the shared loadPageData
  loader (same init music navigation uses) instead of a video subpage.
- CSS reveals #settings-page (and hides the video host) when
  data-side=video + data-video-page=video-settings; id selector outranks the
  blanket music-page hide. 81 tests green.
2026-06-14 00:17:00 -07:00
BoulderBadgeDad
71f126f9d2 video side: Tools page + scan controls on dashboard (3 modes)
- New Tools page (video nav + subpage, mirrors music tools styling): a Library
  Scan tool card with Incremental / Full Refresh / Deep Scan buttons + a live
  status line. Room for more maintenance jobs later.
- Dashboard Library card now has Refresh (full) + Deep Scan buttons, like the
  music dashboard.
- Shared video-scan.js controller: one place triggers + polls scans for all
  surfaces (wires any [data-video-scan-mode]/[data-video-scan]); emits
  soulsync:video-scan-progress/done. Library/Tools/Dashboard just listen — no
  duplicated fetch/poll. video-library.js refactored onto it; dashboard reloads
  stats on scan-done.
- All isolated IIFEs, data-attr wired (no inline onclick). video-tools added to
  the nav (13 pages). 110 tests green.
2026-06-13 23:34:28 -07:00
BoulderBadgeDad
c84231dd4f video side: build the Dashboard page (mirrors music, isolated data)
Real first video page, reusing music's .dash-grid/.dash-card CSS for an
identical look — but every value is driven by isolated video JS, no music
code referenced.

Sections mirror the music dashboard, adapted:
- Service Status: Media Server / Download Client / Metadata Source
- System Stats: swaps 'Active Syncs' -> 'Disk Usage'; keeps download/speed/
  uptime/memory
- Library: Movies / Shows / Episodes / Disk Size
- Recent Syncs -> Recent Downloads (empty state for now)
- Quick Actions: Add Movie/Show, Watchlist, Downloads (navigate via
  data-video-goto)
- Recent Activity
- No enrichment section, no header sweep animation (per plan)

Mechanics:
- #video-page-host now holds .video-subpage sections; controller toggles one
  at a time and falls back to #video-placeholder-slot for unbuilt pages.
- video-side.js dispatches soulsync:video-page-shown; video-dashboard.js (new
  isolated IIFE) listens and applies a zeroed STUB until video.db exists.
  Single seam to swap for a real /api/video/dashboard fetch later.
- All wiring via data-attrs + addEventListener; no inline onclick (keeps the
  script-split integrity contract intact). 73 tests green.
2026-06-13 19:49:09 -07:00
BoulderBadgeDad
9330d66fcd video side: add Wishlist nav page
Completes the Watchlist+Wishlist pair (same as music). Watchlist monitors
shows/channels for new content; Wishlist is the wanted/missing queue
(movies, one-offs, failed grabs to retry). Placeholder for now.
2026-06-13 19:41:46 -07:00
BoulderBadgeDad
e3d3f453da video side: add Watchlist + Downloads nav pages
Following (Watchlist) and the download queue (Downloads) are core to a
movies/TV/YouTube manager — same names as music so they read intuitively.
Both wired via data-video-page (no inline onclick); placeholder for now.
2026-06-13 19:36:13 -07:00
BoulderBadgeDad
f5c0f3ca31 video side: subtitle -> 'Movies, TV & YouTube' (matches music length, describes the side) 2026-06-13 19:26:39 -07:00
BoulderBadgeDad
ac4bee75ef video side: 'Video Manager' subtitle + animated sliding-pill toggle
- Subtitle on the video side is now 'Video Manager' (was 'Video Sync & Manager').
  Music keeps 'Music Sync & Manager' — sync fits music, not video.
- Toggle is now a proper animated pill: a gradient thumb slides under the active
  side (CSS-driven off body[data-side], spring easing), each side has a small
  icon (music note / film), inset track. Still data-attr wired, no inline onclick.
2026-06-13 19:24:10 -07:00
BoulderBadgeDad
3202197740 video side: Music <-> Video sidebar toggle + video nav shell (isolated)
First slice of the video side, on the experimental branch. Purely additive and
fully isolated from music:
- A Music | Video toggle in the sidebar header; clicking flips body[data-side]
  (remembered in localStorage). The shared shell (logo, user, Support, Version)
  stays; only the nav set + subtitle swap.
- A second sidebar nav (.video-nav) with the video pages — Dashboard, Search,
  Discover, Library, Calendar, Import, Settings, Issues, Help & Docs — shown via
  CSS off body[data-side]. Service Status is hidden on the video side.
- A placeholder content host; real video pages land later.

Isolation contract held: index.html is +51/-0 (no music markup changed), music
JS/CSS untouched, nothing in music references the controller. The controller
(webui/static/video/video-side.js) is a self-contained IIFE wired purely via
addEventListener (no globals, no inline onclick) — so it can't affect music and
doesn't trip the script-split-integrity contract.

Tests: 6 video-shell structural/isolation tests + 64 script-integrity green.
2026-06-13 19:13:54 -07:00