Commit Graph
2 Commits
Author SHA1 Message Date
timothyandClaude Opus 4.8 edbecc01d7 feat(spa): S6b — Plex/Jellyfin/Emby Remote media-source screens (#202)
Build the Remote media-source SPA screens over the S5 foundation, replacing
the MediaSourceEditorPlaceholder for the plex/jellyfin/emby dispatch branches
only (Local branches left for S6a):

- PlexSourceScreen: pin-flow sign-in / fix-credentials / sign-out with the
  §C1 poll state machine — polls GET /api/media-sources/plex every 2s up to
  150s and keeps polling while authorized-but-locked ("finalizing"); the
  terminal success is the lock releasing. Popup-blocked fallback link. Server
  table (Refresh disabled while locked / Edit Libraries / Edit Path
  Replacements) + sign-out content-removal confirm dialog.
- RemoteSourceScreen (shared Jellyfin/Emby): connect / edit-connection /
  disconnect (warning dialog) + server table.
- RemoteConnectionEditScreen (shared): secure key affordance (§C3/finding 1)
  — address prefilled, "leave blank to keep" when hasApiKey, required on first
  connect; stored key never rendered or requested.
- RemoteLibrariesEditScreen (shared): client-side sortable Name + MediaKind
  columns, per-library sync Switch, one Save; draft keyed by (name,mediaKind)
  not id, refetch after save (ids change on disable, §C4a).
- PathReplacementsEditScreen (shared): row list + selected-row edit form,
  add/remove, one Save; both fields required; family remote-path column label.

All editors use the ChannelEditScreen draft/save model + a shared useDirtyGuard
(registerNavigationGuard + beforeunload), Save gated !valid||!dirty||saving,
draft retained on 422/network, destructive actions gated on saving, 409 →
refetch. Colocated tests cover the poll (waiting→finalizing→success asserting
it does NOT stop at authorized&locked, timeout, budget-exhausted), the secure
key affordance, sortable columns, draft-retained-on-422, dirty-guard veto, and
the disconnect/sign-out dialogs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 16:33:41 +02:00
timothyandClaude Opus 4.8 6ce448d265 feat(spa): media-source SPA foundation — shared client, helpers, App-owned popstate wrapper (#202 slice S5)
Owns the shared single-files so the S6a (Local) / S6b (Remote) editor slices touch
disjoint files. Editor screens are stubbed (MediaSourceEditorPlaceholder) for S6.

- web/src/api/mediaSources.ts (+test): client module over the new media-source write
  endpoints (local CRUD + move/path-exists; Plex pin-flow/sign-out; shared remote
  state/connection/libraries/path-replacements/refresh; family = only URL variance),
  DTOs re-exported from generated v1, messageFromMediaSourcesError; barrel export.
- web/src/mediaSources/{familyMeta,paths,pinFlowPoll}.ts (+tests): family labels/routes/
  remote-path column naming (owns RemoteFamily); client-side NormalizePath mirror for
  in-draft dup detection; pure §C1 pin-flow poll state machine (waiting/finalizing/
  success/timeout/budget-exhausted), timer-free and fully unit-tested.
- routing.ts parseLibrariesSubRoute + LibrariesSubRoute union split Local vs Remote.
- App.tsx: libraries route allowSubPaths; LibrariesRouteScreen wrapper dispatching a
  flat switch to placeholders; App-owned popstate (finding 4) — App is the single
  popstate owner, consults canLeaveCurrentScreen() and only on approval updates
  librariesSubPath passed DOWN to the wrapper (wrapper never self-listens); state write
  scoped to the libraries route so Playouts/Media pops stay byte-identical (nit 3).
- LibrariesScreen hub wiring: Add-Source menu (Local/Plex/Jellyfin/Emby), remote source
  gear -> family screen, local library row gear -> edit route; removed the disabled
  Scan-All button + the deferred-sources card (§C7/§D.1).
- Tests: App-owned-popstate dirty-guard case (confirm false keeps URL+sub-screen; true
  navigates); mediaSources client URL/verb mapping; pinFlowPoll transitions; familyMeta/
  paths units; hub-wiring navigation.
- docs/spa-conventions.md §8 (resolved sub-path+dirty-guard caveat -> App-owned popstate)
  + §2 exemplar list (guarded-route exception).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 16:11:16 +02:00