Register MediaIndex as a harness concern with ALTER TABLE migration to add
service_id column (idempotent). Scope replace_items by service_id (FIXES latent
global-clear bug where building for one Jellyfin wiped another's rows). Scope
query by service_id (empty-string = all rows, backward-compat). Thread
service_id through build_media_index + worker + query_media router. New
regression test proves scoped replace preserves other services' rows.
Backend: 321 pytest pass, ruff clean. Frontend: build green.
The build endpoint had Depends(get_jellyfin_client) and Depends(get_user_id)
which executed BEFORE the function body. If Jellyfin was unreachable, these
raised HTTPException(503), the function never ran, and the worker was never
started. The frontend mutation had no onError handler, so the failure was
completely silent — the button briefly showed 'Building...' then reverted
to 'Build index' with zero feedback.
Backend fix: removed the Jellyfin dependencies from post_build_index.
The worker subprocess resolves its own Jellyfin connection via
_resolve_jellyfin(service_id) — the endpoint just needs to start the
worker process. The libraries count starts at 0 and gets updated by
the worker once it connects.
Frontend fix: added onError to useBuildIndex that invalidates the status
query (so the UI reflects the non-building state). MediaTab now displays
the build error inline: 'Build failed: <message>' next to the button.
283 backend tests pass (updated build test for new no-dependency flow);
128 frontend tests pass; ruff/eslint clean.
Four fixes:
1. Settings > Services tab: master/detail layout. Replaced the vertical
stack of ServiceConfigEditor cards with a SelectionRailCard (list on
left) + SectionCard (details on right) — same pattern as Machines
and SSH Keys tabs. Click a service in the rail to edit it.
2. Service Overview widget restriction. When adding widgets on a
service's Overview, the dialog now only shows built-in widgets +
widgets for THAT service type (not all services). Dashboard/named
dashboards (no serviceId) still see all.
3. Reorder buttons fixed. The swap-sort_order approach was a no-op when
both items had sort_order=0 (the default). Now moveInstance
renumbers ALL items by their new index position (i * 10) after the
swap, guaranteeing values change. References use updateRef, owned
widgets use saveWidget, both sequential.
4. Media index worker resolution. The subprocess worker called
get_jellyfin_client/get_user_id (FastAPI request dependencies) which
don't work outside request context. Now accepts a service_id
parameter (passed from post_build_index) and resolves the Jellyfin
instance directly from the settings store via _resolve_jellyfin.
Raises RuntimeError (not HTTPException) on failure.
283 backend tests pass; 128 frontend tests pass; ruff/eslint clean.
- backend/ uses proper Python src layout (src/media_library_viewer_api/)
with pyproject.toml, hatchling build, and PYTHONPATH=src convention
- frontend/ is a Vite + React + TypeScript SPA
- archive/ preserves the original Streamlit prototype for reference
- Cleaned up root to only contain docs, license, and subproject dirs
- Updated README for the new dual-subproject architecture