Replace the MediaTab and RequestsTab stubs with real implementations on
the Jellyfin service page.
MediaTab (pages/service-tabs/MediaTab.tsx): lifts the operational content
from the top-level Media page into an instance-scoped tab. Build controls,
status display, library counts, media DataTable, and pagination all read
the Jellyfin service id directly from the instance prop (replacing the old
URL-search-param service selector + dropdown). Row-click navigation to
the file browser is preserved (note: target /files is a cross-slice
dependency on slice 6's FilesTab).
RequestsTab (pages/service-tabs/RequestsTab.tsx): reads the absorbed
jellyseerr_url + jellyseerr_api_key from the Jellyfin instance config.
When unconfigured, renders a CTA to add the fields via the Config tab.
When configured, shows the Jellyseerr URL + an honest placeholder (no
requests backend endpoint exists yet -- out of scope for this slice).
service-tabs/index.ts updated to wire the new components; the
MediaTabStub/RequestsTabStub removed from stubs.tsx.
Note: this branch is based on main, not on mobile-responsive-parity, so
MediaTab lifts main's DataTable + TanStack column-visibility mobile
hiding (no MobileCardRow -- that lands when the branches reconcile).
Tests: MediaTab (instance-scoped hooks + build controls + table render)
+ RequestsTab (configured URL vs empty-state CTA). 90 tests pass (+6);
lint/build green.
Cross-slice flag: MediaTab row-click -> /files will 404 until slice 6
re-routes it to the ssh_tasks FilesTab.
Refs openspec/changes/services-as-hub-ia/ (spec R2.4, tasks slice 5).
The IA shell lands. The static navItems array is replaced by useNavItems(),
which combines useServiceInstances (enabled instances) + useDashboards to
build the nav in spec order: Main Dashboard, named dashboards, conditional
service-type entries (one per configured type; ssh_tasks contributes Files
+ Actions, nextcloud contributes none), Services, Settings.
Legacy top-level routes (/media, /files, /actions, /users, /observability,
/backups, /monitoring, /applications) are removed; a NotFoundPage catch-all
returns 404 (R4.7).
ServicePage is refactored to a tab skeleton: Overview | type-specific
content tabs | Widgets | Config. serviceContentTabs(type) returns the
per-type set (jellyfin=Media+Requests, ssh_tasks=Files+Actions, backups=Jobs,
authentik=Users+Messaging, alertmanager=Alerts, grafana=Links,
prometheus=Metrics, nextcloud=none). Content tabs are stubs ('coming soon');
real content migrates in slices 5-9. Widgets + Config tabs preserve the
existing widget-list and config/secrets editing verbatim.
ServiceTypePage resolves /services/:type (no id) by redirecting to the
first enabled instance; empty state when none.
Instance switcher (Select) appears when >1 ENABLED sibling of the same
type exists (R3.1).
Empty states: Dashboard shows an 'Add a service' CTA when no instances
exist; ServicesPage already had a strong empty state.
Fixes from Slice 4 review:
- B1 (blocker): secret editing regressed because buildInput() hardcoded
secrets:{} after the ConfigBody lift orphaned draftSecrets. Lifted
draftSecrets to the parent ServicePage; buildInput now sends only the
non-blank typed drafts ('leave blank to keep' semantics restored).
- S1: switcher trigger keys off enabled siblings, not total.
New: navEntries.ts + test, dashboards api/hook, service-tabs/ stubs +
index, ServiceTypePage, ServicePage tab skeleton + ConfigBody lift,
Dashboard empty-state CTA, ServicePage tab/switcher/secret-save tests.
Note: this branch is based on main (mobile-responsive-parity is unmerged);
the mobile SheetForm on ServicePage will be re-added when content tabs
get real content (slices 5-9). 84 tests pass (+1 secret-save guard);
lint/build green.
Refs openspec/changes/services-as-hub-ia/ (spec R1-R4/R9, tasks slice 4).