# New widgets: Jellyfin now_playing + Grafana panel embed ## Files changed (10 files, ~390 lines) | File | Status | Lines | |------|--------|-------| | `backend/src/media_library_viewer_api/integrations/jellyfin.py` | modified | +12 (new widget kind + config model) | | `backend/src/media_library_viewer_api/integrations/grafana.py` | modified | +17 (new widget kind + config model) | | `backend/src/media_library_viewer_api/widgets/sources.py` | modified | +12 (now_playing filter + panel embed URL) | | `backend/tests/test_services.py` | modified | +3 (updated widget-kind assertions) | | `backend/tests/test_widgets.py` | modified | +85 (import + 6 new tests) | | `frontend/src/widgets/JellyfinNowPlayingWidget.tsx` | new | 41 | | `frontend/src/widgets/GrafanaPanelWidget.tsx` | new | 50 | | `frontend/src/integrations/registry.ts` | modified | +24 (2 new widget bindings) | | `frontend/src/integrations/registry.test.ts` | modified | +1 (updated grafana kinds) | | `frontend/src/widgets/__tests__/JellyfinNowPlayingWidget.test.tsx` | new | 72 | | `frontend/src/widgets/__tests__/GrafanaPanelWidget.test.tsx` | new | 72 | ## Session-filter logic for now_playing ```python if widget_kind == "now_playing": sessions = [ s for s in sessions if s.get("NowPlayingItem") and not s.get("PlayState", {}).get("IsPaused", True) ] ``` Filters raw Jellyfin sessions BEFORE `_map_sessions_to_activity_rows`. A session is "actively playing" when it has a `NowPlayingItem` (something is playing, not just idle) AND `PlayState.IsPaused` is false. The `activity` kind (default) is unchanged — shows all sessions including idle and paused. ## Embed URL format for panel ```python embed_url = f"{base_url}/d-solo/{dashboard_uid}/manage?panelId={panel_id}&from={from_ts}&to={to_ts}&kiosk=tv" ``` Uses Grafana's `/d-solo/` endpoint which renders a single panel without dashboard chrome. `kiosk=tv` hides the top nav. Defaults: `from_ts="now-1h"`, `to_ts="now"`. ## Validation ``` cd backend && .venv/bin/ruff check src/ tests/ → All checks passed! cd backend && .venv/bin/python -m pytest tests/ → 278 passed, 2 warnings (pre-existing) cd frontend && npm run lint → 0 errors, 0 warnings cd frontend && npm run build → ✓ built (tsc + vite) cd frontend && npm run test → 39 files / 127 tests passed ``` Backend: +6 new tests (definition assertions x2, grafana panel URL x2, jellyfin now_playing filter x1, jellyfin activity shows all x1). Frontend: +6 new tests (JellyfinNowPlayingWidget x3, GrafanaPanelWidget x3). ## Deviations 1. **No deviations from spec.** Both widgets are additive — no existing behavior changed. The `activity` and `link` kinds work exactly as before. 2. **GrafanaPanelWidget pi-lens advisory** for `