From dd12631f074712f30e899bc421ccd076ef295f75 Mon Sep 17 00:00:00 2001 From: Alex Blank Date: Tue, 12 May 2026 15:19:25 +0200 Subject: [PATCH] cleaned up --- .fusion/archive.db | Bin 4096 -> 0 bytes .fusion/archive.db-shm | Bin 32768 -> 0 bytes .fusion/archive.db-wal | Bin 271952 -> 0 bytes .fusion/config.json | 127 ------------------ .fusion/fusion.db | Bin 933888 -> 0 bytes .fusion/fusion.db-shm | Bin 32768 -> 0 bytes .fusion/fusion.db-wal | Bin 432632 -> 0 bytes .fusion/memory/2026-05-11.md | 3 - .fusion/memory/DREAMS.md | 3 - .fusion/memory/MEMORY.md | 19 --- .gitignore | 6 +- .stoneforge/.dashboard-opened | 2 - .stoneforge/.gitignore | 6 - .stoneforge/config.yaml | 20 --- .stoneforge/sync/dependencies.jsonl | 8 -- .stoneforge/sync/elements.jsonl | 8 -- .../435525-1778528804/content/overview.html | 50 ------- .../content/visual-direction.html | 38 ------ .../435525-1778528804/content/waiting.html | 3 - .../435525-1778528804/state/server-info | 1 - .../435525-1778528804/state/server.pid | 1 - 21 files changed, 5 insertions(+), 290 deletions(-) delete mode 100644 .fusion/archive.db delete mode 100644 .fusion/archive.db-shm delete mode 100644 .fusion/archive.db-wal delete mode 100644 .fusion/config.json delete mode 100644 .fusion/fusion.db delete mode 100644 .fusion/fusion.db-shm delete mode 100644 .fusion/fusion.db-wal delete mode 100644 .fusion/memory/2026-05-11.md delete mode 100644 .fusion/memory/DREAMS.md delete mode 100644 .fusion/memory/MEMORY.md delete mode 100644 .stoneforge/.dashboard-opened delete mode 100644 .stoneforge/.gitignore delete mode 100644 .stoneforge/config.yaml delete mode 100644 .stoneforge/sync/dependencies.jsonl delete mode 100644 .stoneforge/sync/elements.jsonl delete mode 100644 .superpowers/brainstorm/435525-1778528804/content/overview.html delete mode 100644 .superpowers/brainstorm/435525-1778528804/content/visual-direction.html delete mode 100644 .superpowers/brainstorm/435525-1778528804/content/waiting.html delete mode 100644 .superpowers/brainstorm/435525-1778528804/state/server-info delete mode 100644 .superpowers/brainstorm/435525-1778528804/state/server.pid diff --git a/.fusion/archive.db b/.fusion/archive.db deleted file mode 100644 index 4d0f4d75c0c482c3485d3529b474a8f56497a2ca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4096 zcmWFz^vNtqRY=P(%1ta$FlG>7U}9o$P*7lCU|@t|AVoG{WYFvV%?r{lK(-m98b?E5 nGz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nC=3ArvhW9) diff --git a/.fusion/archive.db-shm b/.fusion/archive.db-shm deleted file mode 100644 index a10e4e3c32c2983edf7c382e0e097dcba1372dd7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32768 zcmeI*H%{qd;U zNS<5$G3v|OsZZkFwQoqrH4~MM%0>Nly|FJj*O>o2pO5wCsFwQuWKW}7W8N0k9@P<* z z;e7>i(dn??H$KU&1qQ9t6W&{3$U8pitp$dy(-YoXU_x^$YeRcF@rEG1p}?f(RneyQ zb!wQN$ld}|TF{ENbf7bD2+|u0OlwiA+SZ}Y4bv0ZTVO^@T8lp|9Vz(>69WMR5I_I{ Z1Q0*~0R#|0009ILKmY**5J12|;2ZF&H0uBW diff --git a/.fusion/archive.db-wal b/.fusion/archive.db-wal deleted file mode 100644 index 91c3645105d5bd5cfbf73d07a99e5702703924d9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271952 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 Hr6TbE1V$H+ diff --git a/.fusion/config.json b/.fusion/config.json deleted file mode 100644 index 4cfe44f..0000000 --- a/.fusion/config.json +++ /dev/null @@ -1,127 +0,0 @@ -{ - "nextId": 1, - "settings": { - "globalPause": false, - "enginePaused": false, - "maxConcurrent": 2, - "maxTriageConcurrent": 2, - "globalMaxConcurrent": 4, - "maxWorktrees": 4, - "pollIntervalMs": 15000, - "heartbeatMultiplier": 1, - "groupOverlappingFiles": true, - "overlapIgnorePaths": [], - "autoMerge": true, - "mergeStrategy": "direct", - "requirePrApproval": false, - "pushAfterMerge": false, - "pushRemote": "origin", - "unavailableNodePolicy": "block", - "recycleWorktrees": false, - "worktreeNaming": "random", - "taskPrefix": "FN", - "includeTaskIdInCommit": true, - "commitAuthorEnabled": true, - "commitAuthorName": "Fusion", - "commitAuthorEmail": "noreply@runfusion.ai", - "modelPresets": [], - "autoSelectModelPreset": false, - "completionDocumentationMode": "off", - "defaultPresetBySize": {}, - "autoResolveConflicts": true, - "smartConflictResolution": true, - "mergerAutostashMaxAgeHours": 24, - "worktreeRebaseBeforeMerge": true, - "worktreeRebaseRemote": "", - "worktreeRebaseLocalBase": true, - "mergeConflictStrategy": "smart-prefer-main", - "workflowStepTimeoutMs": 360000, - "strictScopeEnforcement": false, - "buildRetryCount": 0, - "verificationFixRetries": 3, - "buildTimeoutMs": 300000, - "requirePlanApproval": false, - "specStalenessEnabled": false, - "specStalenessMaxAgeMs": 21600000, - "aiSessionTtlMs": 604800000, - "aiSessionCleanupIntervalMs": 3600000, - "autoUnpauseEnabled": true, - "autoUnpauseBaseDelayMs": 300000, - "autoUnpauseMaxDelayMs": 3600000, - "maxStuckKills": 6, - "preserveProgressOnStuckRequeue": true, - "maxPostReviewFixes": 1, - "maxSpawnedAgentsPerParent": 5, - "maxSpawnedAgentsGlobal": 20, - "maintenanceIntervalMs": 300000, - "autoArchiveDoneTasksEnabled": true, - "autoArchiveDoneAfterMs": 172800000, - "archiveAgentLogMode": "compact", - "autoUpdatePrStatus": false, - "githubCommentOnDone": false, - "githubTrackingEnabledByDefault": false, - "githubAuthMode": "gh-cli", - "autoBackupEnabled": false, - "autoBackupSchedule": "0 2 * * *", - "autoBackupRetention": 7, - "autoBackupDir": ".fusion/backups", - "memoryBackupEnabled": false, - "memoryBackupSchedule": "0 3 * * *", - "memoryBackupRetention": 14, - "memoryBackupDir": ".fusion/backups/memory", - "memoryBackupScope": "all", - "autoSummarizeTitles": false, - "useAiMergeCommitSummary": true, - "insightExtractionEnabled": false, - "insightExtractionSchedule": "0 2 * * *", - "insightExtractionMinIntervalMs": 86400000, - "taskEvaluationEnabled": false, - "taskEvaluationSchedule": "0 5 * * *", - "taskEvaluationFollowUpPolicy": "off", - "memoryEnabled": true, - "memoryBackendType": "qmd", - "memoryAutoSummarizeEnabled": false, - "memoryAutoSummarizeThresholdChars": 50000, - "memoryAutoSummarizeSchedule": "0 3 * * *", - "memoryDreamsEnabled": false, - "memoryDreamsSchedule": "0 4 * * *", - "runStepsInNewSessions": false, - "maxParallelSteps": 2, - "missionStaleThresholdMs": 600000, - "missionMaxTaskRetries": 3, - "missionHealthCheckIntervalMs": 300000, - "reflectionEnabled": false, - "reflectionIntervalMs": 3600000, - "reflectionAfterTask": true, - "reviewHandoffPolicy": "disabled", - "showQuickChatFAB": false, - "researchSettings": { - "enabled": true, - "enabledSources": { - "webSearch": true, - "pageFetch": true, - "github": false, - "localDocs": true, - "llmSynthesis": true - }, - "limits": { - "maxConcurrentRuns": 3, - "maxSourcesPerRun": 20, - "maxDurationMs": 300000, - "requestTimeoutMs": 30000 - } - }, - "evalSettings": { - "enabled": false, - "intervalMs": 86400000, - "followUpPolicy": "suggest-only", - "retentionDays": 30 - }, - "researchEnabled": true, - "researchMaxConcurrentRuns": 3, - "researchDefaultTimeout": 300000, - "researchMaxSourcesPerRun": 20, - "researchMaxSynthesisRounds": 2, - "globalPauseReason": "manual" - } -} \ No newline at end of file diff --git a/.fusion/fusion.db b/.fusion/fusion.db deleted file mode 100644 index eb9169efb485a36ed6e2145152662e61fee25f71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 933888 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 VfB*pk1PBlyK!5-N0t7Y{_yLb|IH>>t diff --git a/.fusion/fusion.db-wal b/.fusion/fusion.db-wal deleted file mode 100644 index b59be02471b468dcc30ef4a5b9a5c36c0eefabce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 432632 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 diff --git a/.fusion/memory/DREAMS.md b/.fusion/memory/DREAMS.md deleted file mode 100644 index 1e1f4f5..0000000 --- a/.fusion/memory/DREAMS.md +++ /dev/null @@ -1,3 +0,0 @@ -# Memory Dreams - - diff --git a/.fusion/memory/MEMORY.md b/.fusion/memory/MEMORY.md deleted file mode 100644 index 3407ce8..0000000 --- a/.fusion/memory/MEMORY.md +++ /dev/null @@ -1,19 +0,0 @@ -# Project Memory - - - -## Architecture - - - -## Conventions - - - -## Pitfalls - - - -## Context - - diff --git a/.gitignore b/.gitignore index 62e51dd..ee3e8de 100644 --- a/.gitignore +++ b/.gitignore @@ -47,4 +47,8 @@ frontend/dist/ # Pi internal .pi-lens/ .pi/ -/.stoneforge/.worktrees/ + +# Tool metadata +.fusion/ +.stoneforge/ +.superpowers/ diff --git a/.stoneforge/.dashboard-opened b/.stoneforge/.dashboard-opened deleted file mode 100644 index 8dfba23..0000000 --- a/.stoneforge/.dashboard-opened +++ /dev/null @@ -1,2 +0,0 @@ -380698 -1778526681407 diff --git a/.stoneforge/.gitignore b/.stoneforge/.gitignore deleted file mode 100644 index 3ed7a69..0000000 --- a/.stoneforge/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# Runtime data -*.db -*.db-journal -*.db-wal -*.db-shm -daemon-state.json diff --git a/.stoneforge/config.yaml b/.stoneforge/config.yaml deleted file mode 100644 index 24f7fc5..0000000 --- a/.stoneforge/config.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# Stoneforge Configuration - -database: stoneforge.db -sync: - auto_export: true - elements_file: elements.jsonl - dependencies_file: dependencies.jsonl -playbooks: - paths: - - playbooks -identity: - mode: soft -merge: - auto_merge: true - target_branch: stoneforge/review - require_approval: false -workflow: - preset: review -agents: - permission_model: unrestricted diff --git a/.stoneforge/sync/dependencies.jsonl b/.stoneforge/sync/dependencies.jsonl deleted file mode 100644 index 74ba382..0000000 --- a/.stoneforge/sync/dependencies.jsonl +++ /dev/null @@ -1,8 +0,0 @@ -{"blockedId":"el-1hq","blockerId":"el-401","type":"parent-child","createdAt":"2026-05-11T19:25:05.357Z","createdBy":"el-2rt3"} -{"blockedId":"el-5r1","blockerId":"el-401","type":"parent-child","createdAt":"2026-05-11T19:27:55.342Z","createdBy":"el-2rt3"} -{"blockedId":"el-3qf","blockerId":"el-401","type":"parent-child","createdAt":"2026-05-11T19:27:55.727Z","createdBy":"el-2rt3"} -{"blockedId":"el-32h","blockerId":"el-401","type":"parent-child","createdAt":"2026-05-11T19:27:56.147Z","createdBy":"el-2rt3"} -{"blockedId":"el-1fr","blockerId":"el-401","type":"parent-child","createdAt":"2026-05-11T19:27:56.551Z","createdBy":"el-2rt3"} -{"blockedId":"el-580","blockerId":"el-401","type":"parent-child","createdAt":"2026-05-11T19:27:56.956Z","createdBy":"el-2rt3"} -{"blockedId":"el-3ub","blockerId":"el-401","type":"parent-child","createdAt":"2026-05-11T19:27:57.377Z","createdBy":"el-2rt3"} -{"blockedId":"el-2oe","blockerId":"el-401","type":"parent-child","createdAt":"2026-05-11T19:27:57.816Z","createdBy":"el-2rt3"} diff --git a/.stoneforge/sync/elements.jsonl b/.stoneforge/sync/elements.jsonl deleted file mode 100644 index 20300f3..0000000 --- a/.stoneforge/sync/elements.jsonl +++ /dev/null @@ -1,8 +0,0 @@ -{"id":"el-1hq","type":"document","createdAt":"2026-05-11T19:25:05.349Z","updatedAt":"2026-05-11T19:27:59.893Z","createdBy":"el-2rt3","tags":[],"metadata":{"purpose":"document-directory"},"title":"Documentation Directory","contentType":"markdown","content":"# Documentation Directory\n\nIndex of all workspace documents.\n\n## Specs\n\n| ID | Title |\n|----|-------|\n| el-32h | Requirements and Decision Log |\n| el-1fr | Migration Plan: Streamlit to FastAPI + React |\n\n## References\n\n| ID | Title |\n|----|-------|\n| el-1hq | Documentation Directory (this document) |\n| el-5r1 | Project README |\n| el-3qf | AGENTS.md |\n| el-3ub | Backend API Reference |\n| el-2oe | Frontend Reference |\n\n## How-To Guides\n\n| ID | Title |\n|----|-------|\n| el-580 | Contributing Guide |\n\n## Explanations\n\n(none yet)\n\n## Decision Logs\n\n(none yet — see Requirements and Decision Log el-32h for the living decision log)","version":3,"previousVersionId":"el-1hq","category":"reference","status":"active","immutable":false} -{"id":"el-5r1","type":"document","createdAt":"2026-05-11T19:27:41.573Z","updatedAt":"2026-05-11T19:27:41.573Z","createdBy":"el-2rt3","tags":[],"metadata":{},"title":"Project README","contentType":"markdown","content":"# Manage\n\nManage is a media and server operations tool with Jellyfin integration, SSH file inspection, server monitoring, and safe remote job templates.\n\nSee `docs/REQUIREMENTS.md` for the living requirements, decisions, and planning history.\nSee `docs/MIGRATION_PLAN.md` for the FastAPI + React architecture plan.\n\nProject policy/docs:\n\n- License: `LICENSE` (MIT)\n- Contributing guide: `CONTRIBUTING.md`\n\n## Architecture\n\nThe project consists of two subprojects:\n\n- **`backend/`** — FastAPI Python API (see `backend/README.md`)\n- **`frontend/`** — React + TypeScript SPA (see `frontend/README.md`)\n- **`archive/`** — Original Streamlit prototype (preserved for reference)\n\n## Features\n\n- Dashboard with now-playing sessions, server monitoring overview, and per-library media counts\n- Server monitoring with CPU, IO wait, RAM, network, and disk I/O charts plus a sortable dashboard table covering all configured machines\n- Per-machine monitoring settings with local and remote targets managed in the UI, plus backend-collected recent action history per machine\n- SQLite-indexed media table with full-library sort/filter\n- Read-only Users tab with Jellyfin as the base source and optional Jellyseerr enrichment\n- Remote file browser with ffprobe preview and job execution\n- Jellyfin API integration for library metadata and user identity data\n- SSH-based file inspection and remote job templates\n\n## Quick Start\n\n### Docker Compose (recommended)\n\nProduction-style deployment with the frontend serving the SPA and proxying `/api` to the backend. The compose files rely on environment-variable interpolation, so export the required values in your shell before running them (no `env_file` is needed):\n\n```bash\ndocker compose up --build\n```\n\nOpen the app at http://localhost:8080.\n\nLocal development with hot reload:\n\n```bash\ndocker compose -f docker-compose.dev.yml up --build\n```\n\nFrontend runs on http://localhost:5173 and the backend on http://localhost:8000.\nThe backend media index is persisted in a Docker volume (`backend_cache`) so rebuilds and container restarts do not force a full re-index.\nMonitoring machine definitions and recent machine activity are stored in the backend so the UI can show one section per configured machine and preserve history across restarts.\n\n### Manual backend/frontend development\n\n```bash\ncd backend\npython -m venv .venv\nsource .venv/bin/activate\npip install -e '.[dev]'\nuvicorn media_library_viewer_api.main:app --reload --port 8000\n```\n\n```bash\ncd frontend\nnpm install\nnpm run dev\n```\n\n## Configuration\n\nThe Compose files use environment-variable interpolation. Export the required variables in your shell or pass them inline; a `.env` file is optional, not required.\n\n### Compose examples\n\nProduction-style example with shell exports:\n\n```bash\nexport BACKEND_APP_HOST=manage.example.com\nexport FRONTEND_APP_HOST=manage.example.com\nexport CERT_RESOLVER=letsencrypt\nexport VITE_OIDC_ISSUER=https://authentik.example/application/o/manage/\nexport VITE_OIDC_CLIENT_ID=manage\nexport VITE_OIDC_REDIRECT_URI=https://manage.example.com/\nexport VITE_OIDC_POST_LOGOUT_REDIRECT_URI=https://manage.example.com/\n\ndocker compose up --build\n```\n\nInline one-liner example:\n\n```bash\nBACKEND_APP_HOST=manage.example.com FRONTEND_APP_HOST=manage.example.com CERT_RESOLVER=letsencrypt VITE_OIDC_ISSUER=https://authentik.example/application/o/manage/ VITE_OIDC_CLIENT_ID=manage VITE_OIDC_REDIRECT_URI=https://manage.example.com/ VITE_OIDC_POST_LOGOUT_REDIRECT_URI=https://manage.example.com/ docker compose up --build\n```\n\nFor local development, no SSH key is required unless you want to connect to remote SSH machines later:\n\n```bash\ndocker compose -f docker-compose.dev.yml up --build\n```\n\nExample environment variables:\n\n```bash\n# Optional backend logging level\nLOG_LEVEL=INFO\n\n# Optional SMTP settings for the Users -> message popup\nSMTP_HOST=smtp.example.com\nSMTP_PORT=587\nSMTP_USERNAME=your-smtp-username\nSMTP_PASSWORD=your-smtp-password\nSMTP_FROM_ADDRESS=no-reply@example.com\nSMTP_FROM_NAME=Manage\nSMTP_USE_TLS=true\nSMTP_USE_SSL=false\nSMTP_TIMEOUT=30\n\n# Jellyfin, Jellyseerr, and SSH targets are now configured per machine in the app's Settings tab.\n# The backend seeds a local machine automatically, so no global Jellyfin or SSH env vars are required.\n#\n# Remote SSH machines can store their private key and optional passphrase directly in Settings,\n# so no SSH key mount is required for normal use.\n\n# Authentik / OIDC\nAUTH_ENABLED=true\nOIDC_ISSUER_URL=https://authentik.example/application/o/media-library-viewer/\nOIDC_AUDIENCE=media-library-viewer\nOIDC_JWKS_URL=\nOIDC_CLOCK_SKEW_SECONDS=30\n\n# Frontend OIDC settings\nVITE_OIDC_ENABLED=true\nVITE_OIDC_ISSUER=https://authentik.example/application/o/media-library-viewer/\nVITE_OIDC_CLIENT_ID=media-library-viewer\nVITE_OIDC_SCOPE=openid profile email\nVITE_OIDC_REDIRECT_URI=http://localhost:8080/\nVITE_OIDC_POST_LOGOUT_REDIRECT_URI=http://localhost:8080/\n```\n\n## Remote server requirements\n\nThe remote server needs:\n\n- Linux `/proc` and `/sys/block` for monitoring\n- `/bin/sh` (POSIX shell)\n- `python3`, `ffprobe`, `find`, `stat`, `df`, `awk`\n\nThe SSH client rejects unknown host keys. Connect manually once first:\n\n```bash\nssh user@host\n```\n\n## Development\n\n```bash\n# Backend\ncd backend && PYTHONPATH=src python -m py_compile src/media_library_viewer_api/main.py\n\n# Frontend\ncd frontend && npx tsc --noEmit && npm run build\n```\n\n## Notes\n\n- Jellyfin server root URL required (not `/web`). The client strips trailing `/web` defensively.\n- SSH commands run through `/bin/sh -c` regardless of remote login shell.\n- Job templates are shell-quoted. Add new templates in `backend/src/media_library_viewer_api/jobs.py`.\n- Monitoring collector uses JSONL in `/tmp`, pruned to 7 days / 70k lines.\n- Root-level Docker Compose files are provided for production (`docker-compose.yml`) and local development (`docker-compose.dev.yml`), and both rely on Compose interpolation rather than `env_file` entries.","version":1,"previousVersionId":null,"category":"reference","status":"active","immutable":false} -{"id":"el-3qf","type":"document","createdAt":"2026-05-11T19:27:42.804Z","updatedAt":"2026-05-11T19:27:42.804Z","createdBy":"el-2rt3","tags":[],"metadata":{},"title":"AGENTS.md","contentType":"markdown","content":"# AGENTS.md\n\n## Layout\n- Current app is `backend/` (FastAPI) plus `frontend/` (Vite React); ignore Streamlit-era commands in `CONTRIBUTING.md`.\n- Backend entrypoint: `backend/src/media_library_viewer_api/main.py` (`media_library_viewer_api.main:app`).\n- Frontend entrypoint: `frontend/src/main.tsx`.\n- Backend uses a `src/` layout; tests live in `backend/tests/`.\n\n## Commands\n- Backend setup: `cd backend && python -m venv .venv && source .venv/bin/activate && pip install -e '.[dev]'`\n- Backend run: `uvicorn media_library_viewer_api.main:app --reload --port 8000`; if not installed, use `PYTHONPATH=src uvicorn media_library_viewer_api.main:app --reload --port 8000`.\n- Backend tests: run `pytest` from `backend/`; focused checks can use `pytest tests/test_api.py` or `pytest -k `; if the package is not installed, use `PYTHONPATH=src pytest`.\n- Frontend setup: `cd frontend && npm install`\n- Frontend dev/build/lint: `npm run dev`, `npm run build`, `npm run lint`; `npm run build` already typechecks via `tsc -b`.\n- Focused frontend typecheck: `npx tsc --noEmit`\n- Local dev stack: `docker compose -f docker-compose.dev.yml up --build`\n- Production stack: `docker compose up --build`\n\n## Repo-Specific Gotchas\n- Root compose files rely on environment-variable interpolation, not `env_file`; export required values before running them.\n- Production compose needs the host/cert and OIDC variables from `docker-compose.yml` (`BACKEND_APP_HOST`, `FRONTEND_APP_HOST`, `CERT_RESOLVER`, and the frontend OIDC vars).\n- Dev compose runs with auth off and does not need SSH key material unless you add remote SSH machines.\n- `backend_cache` persists the media index and the managed `known_hosts` file.\n- SSH host-key checking is strict, but the first successful connect records the host key into backend-managed `known_hosts`.\n- Backend startup validates auth settings, rebuilds managed `known_hosts`, and starts the mail queue and monitoring poller.\n- Machine-level settings now own Jellyfin/Jellyseerr/SSH config; the backend seeds a local machine automatically.\n- Remote job templates live in `backend/src/media_library_viewer_api/jobs.py`; keep shell quoting intact.\n- Backend Ruff config is in `backend/pyproject.toml` and uses line length 120 with Python 3.11.\n- Update `docs/REQUIREMENTS.md` whenever behavior, UX, or architecture changes.\n- Do not commit `.env`, `.streamlit/secrets.toml`, private keys, or tokens.","version":1,"previousVersionId":null,"category":"reference","status":"active","immutable":false} -{"id":"el-32h","type":"document","createdAt":"2026-05-11T19:27:43.621Z","updatedAt":"2026-05-11T19:27:43.621Z","createdBy":"el-2rt3","tags":[],"metadata":{},"title":"Requirements and Decision Log","contentType":"markdown","content":"# Manage - Requirements and Decision Log\n\nThis is a living document for the project. Update it whenever requirements, UX expectations, architecture, constraints, or implementation plans change.\n\n## Product Goal\n\nBuild Manage, a compact web application for browsing a remote Jellyfin media library and inspecting the corresponding media files on disk over SSH. The app should support library metadata review, direct file-system navigation, detailed media metadata inspection, and safe remote maintenance/job workflows.\n\n## Current Phase\n\nPhase 1: Jellyfin media index, SSH-based remote filesystem inspection, server monitoring, and safe job templates.\n\n## Core Requirements\n\n### Jellyfin Library\n\n- Connect to a remote Jellyfin server using an API key.\n- Use the Jellyfin server root URL, not the `/web` UI URL.\n- Handle API-key auth correctly:\n - use `GET /Users` to list available users;\n - allow a manual `JELLYFIN_USER_ID` override;\n - do not rely on `/Users/Me` for API-key auth.\n- List Jellyfin libraries for the selected user.\n- Browse library items with search, pagination, media type filtering, and poster cards.\n- Show item details including overview, genres, ratings where available, file path, media sources/streams, and raw Jellyfin JSON.\n- Provide a Media tab with a paginated inventory table for large libraries.\n- Media inventory should show title, series name, season, episode number, type, year, runtime, file size, bitrate, explicit HDR yes/no flag, video codec, resolution, date added, and path where available.\n- Media inventory should use Jellyfin metadata only for now; full ffprobe enrichment for all media should be deferred to a cached/background scan to avoid expensive per-item SSH probing.\n- Media inventory should support multi-library selection, type, search, full-index sort/order, HDR filter, page size, and page controls.\n- Media inventory should use a local SQLite index so sorting/filtering by nested/derived fields such as size, bitrate, HDR, codec, resolution, series, season, and episode can apply to the whole indexed library instead of only one Jellyfin page.\n- Media inventory table should be read-only; full-index sorting/filtering should be handled by the service/query layer rather than relying on frontend table sorting.\n- Media inventory should use row-based table selection and automatically sync the File browser tab to the selected row's containing directory.\n- Media row selection should update both Media selected-row state and File browser location without requiring an extra action button.\n- File browser interaction should stay explicit and simple: read-only listing plus explicit Open/Select actions rather than another row-selection grid.\n- Use valid Jellyfin `Fields` query values only, because invalid field names can cause `400 Bad Request` responses.\n\n### Users & Communication\n\n- Provide a Users tab that lists all available users the system knows about.\n- Use Jellyfin as the base source of truth for the user list.\n- Optionally enrich Jellyfin users with Jellyseerr data when Jellyseerr is configured and reachable.\n- Be tolerant of Jellyseerr response-shape differences across versions; for example, some endpoints may return a wrapped `{ users: [...] }` payload instead of a raw list.\n- Surface whatever contact/identity fields are available from the configured source(s), such as email, avatar/thumb, role/permissions, and notification/contact eligibility.\n- Email should only render actual email addresses; usernames or other non-email identifiers should be suppressed instead of shown as email.\n- Keep communication actions separate from listing/identity data so the UI can support future email/notification workflows without redesigning the user list.\n- SMTP-backed user messages should be queued asynchronously and return immediately; delivery must not block the rest of the API request path.\n- The Users tab should expose a live queue status indicator so users can see when the outbound email queue is idle, busy, stopped, or failing.\n- The queue status indicator should clearly show the current queue item count.\n- The Users tab should include a one-click SMTP test action that validates connectivity/authentication without sending a real message.\n- The SMTP test action should visibly show when it is running.\n- The SMTP test should surface the chosen protocol/port and, for Fastmail, try both 465/SSL and 587/STARTTLS so configuration mismatches are easier to diagnose.\n- Rework the Users data into an internal merged state so user identity can be combined with related now-playing/session data.\n- Clicking a now-playing row should navigate to the Users tab and open the matching user detail drawer, keeping the selection deep-linkable.\n- Provide an explicit \"Open in Users\" action in now-playing rows in addition to row-click navigation.\n- Provide a compact per-field source summary for the Users detail drawer so it is obvious which backend source supplied name, email, avatar, and access data.\n- Jellyseerr user list pagination must use `take`/`skip`, not `page`/`pageSize`.\n- The Users tab table should stay compact and readable: center the avatar and email cells, keep backend source diagnostics out of the table itself, and prefer a simpler hand-built row layout when a dense grid makes text positioning awkward.\n- The Users table activity column should stay compact and show only a brief status badge for playing/paused/idle/no-session state instead of a multi-line activity summary.\n- The Dashboard activity panel should reuse the same compact session-table styling as the Users activity details so the two views feel consistent.\n- In the shared session activity table, the user column should come before state, title/type, and device because the user is the most relevant identifier.\n- The shared session table should keep a compact overall status summary line above the rows that reports total sessions plus playing, paused, and idle counts.\n- The shared session table should keep the session identifier under the user name in a caption instead of giving it a full column, to keep the table tighter.\n- The Users tab may open a read-only detail drawer for a selected user, but any communication actions in that drawer should remain clearly disabled/placeholders until the workflow is implemented.\n- The frontend shell should use a polished two-row header with branding on the left, user/logout controls on the right, and primary navigation in a dedicated tab row beneath.\n- The frontend shell and primary pages should remain responsive and mobile-safe, with compact navigation, stacked controls on narrow screens, and reduced table column density where needed.\n- Dense management surfaces such as Actions and Settings should prefer compact tabbed or split-pane layouts over long single-column forms when that improves scanning and editing speed.\n- Tab rails and subtabs should feel like a polished admin console: compact, clearly selected, and visually consistent across the app.\n- When a setting subsection is not applicable to the currently selected mode or service, the UI should hide those fields rather than leaving them visible but disabled.\n- The frontend should hydrate the API bearer token from persisted OIDC user storage immediately on reload so early requests do not race the auth provider lifecycle.\n- The Media tab should persist its search/filter/sort/pagination state across reloads and tab switches.\n- The File Browser should persist its current directory and selected file across reloads and tab switches.\n- Backend startup should log a secret-safe configuration summary and request/activity diagnostics so configuration issues can be debugged without exposing API keys.\n\n### Remote Filesystem over SSH\n\n- Connect to a remote media server via SSH.\n- Use strict SSH host key behavior, but synthesize and persist the managed `known_hosts` file from configured SSH machines instead of requiring users to mount their own `known_hosts` file.\n- Browse remote directories and files rooted at a configurable default media path.\n- File browser handoff should map Jellyfin paths to `REMOTE_MEDIA_ROOT` when possible (for example `/media/...` -> `/srv/media/...` when root is `/srv/media`).\n- Media index paths should be stored in the SSH-visible form by default, using the same Jellyfin-to-SSH mapping so the Media tab and file browser agree on paths.\n- Support a configurable Jellyfin-to-SSH fallback path prefix for cases where `REMOTE_MEDIA_ROOT` mapping alone is not sufficient.\n- Support manual path entry and refresh.\n- Remote file listing must be compact, structured, and navigable.\n- The file table should be read-only.\n- The file table should use row selection (single-select) in an AG Grid format consistent with the Media tab.\n- The file table should not expose a visible checkbox selection column.\n- The file table should not show a visible `selected` column.\n- Include a top `[UP] ..` row, when not at `/`, to navigate to the parent directory.\n- The selected path should be visibly shown outside the table.\n- Selecting a directory row (including `[UP] ..`) should open it immediately.\n- Selecting a file row should set the selected file target for metadata/jobs.\n- Avoid emojis and hard-to-render characters in labels.\n\n### Remote Listing Controls\n\n- Show compact listing summary information:\n - total entries;\n - directories;\n - files;\n - total file size for files in the current directory.\n- Support filtering by:\n - all entries;\n - directories;\n - files;\n - file extension.\n- Support case-insensitive filename search.\n- Support sorting by:\n - name;\n - kind/type;\n - size;\n - modified time.\n- Support ascending/descending sort order.\n- Support pagination and configurable rows per page.\n\n### Disk-Level Metadata\n\n- Run `ffprobe` on selected remote files to inspect authoritative media metadata directly from disk.\n- When a known video/movie file is selected in the remote file browser, automatically run a blocking `ffprobe` preview and show a spinner while it completes.\n- Cache preview results briefly to keep repeated Streamlit reruns responsive; allow users to reload the preview manually.\n- Display `ffprobe` results in separate sections instead of one sparse all-streams table:\n - container/format summary;\n - video streams;\n - audio streams;\n - subtitle streams.\n- Video metadata should include codec, profile, resolution, pixel format, bitrate, frame rate, color range/space/transfer/primaries, side data/HDR-related metadata where available, language, title, and default flag.\n- Audio metadata should include codec, profile, channels/layout, sample rate, bitrate, language, title, default, and forced flags.\n- Subtitle metadata should include codec, language, title, default, forced, and hearing-impaired flags where available.\n- Expose raw `ffprobe` JSON for deeper inspection.\n- Keep a manual `ffprobe` action available for selected paths.\n- Support `stat` on selected paths.\n\n### Dashboard / Server Monitoring\n\n- Provide a dashboard tab with a compact Jellyfin media library overview and a sortable table-style server resource overview covering all configured monitoring machines.\n- Provide a dashboard shortcuts area that can open external websites now and later support internal shortcut types such as saved actions and user deep-links without redesigning the container.\n- Dashboard shortcuts should support a small icon/preview field so cards can be visually recognizable without changing the underlying model later.\n- Support OIDC login in the frontend using an OIDC client library, with backend JWT validation for protected API requests.\n- Persist frontend OIDC auth state across tab reloads by storing the OIDC user and request state in browser localStorage.\n- Provide Docker Compose deployment files at the repository root for production and local development.\n- SSH private keys should be managed as reusable saved secrets in Settings, independent of any one machine, and SSH machines should select from that saved-key list.\n- The web UI should allow both importing an existing private key and generating a new SSH keypair for that saved-key list.\n- Saved SSH keys should display their derived public key, fingerprint, and machine usage count so administrators can audit them at a glance.\n- The app should support optional SSH private key passphrases alongside the stored key material.\n- The Settings tab should include a destructive local-database reset action protected by multiple acknowledgements and a typed confirmation phrase.\n- Production compose should also pass the root `.env` into the backend container so runtime auth settings like `OIDC_ISSUER_URL` are available there, not just at compose interpolation time.\n- The backend media index should persist in a Docker volume so a container restart or image rebuild does not force a new full index build.\n- Compose deployment should not require `env_file`; required values should be supplied through environment interpolation or inline shell exports.\n- The SSH key configuration should move from machine-local text areas to a saved SSH key registry; directory/name inputs remain only as legacy compatibility fields.\n- Show Jellyfin media counts for movies, series, and series episodes on the dashboard.\n- Show dashboard session activity from Jellyfin, including both currently playing sessions and logged-in idle sessions.\n- Activity rows should include user, media title (or `(idle)`), playback state (`playing`/`paused`/`idle`), and whether transcoding is active.\n- Provide a dashboard tab with a compact server resource overview over SSH.\n- Provide a separate Monitoring tab for detailed resource charts, collector controls, diagnostics, raw samples, and per-machine recent action history gathered automatically by the backend.\n- Expose backend poller status/configuration so the dashboard and Monitoring page can surface whether monitoring snapshots are being gathered automatically.\n- The Monitoring tab should present one section per configured machine, and local vs remote machines should be treated the same in the UI with different connection/configuration data.\n- Provide a Settings tab where monitoring machines can be added, edited, enabled/disabled, or deleted persistently.\n- The app should start with no pre-seeded monitoring machines; users must explicitly add a local or SSH target before Monitoring shows anything.\n- Local machines should be supported across Monitoring, Files, Jobs, and other remote-inspection tools without requiring SSH credentials.\n- Creating a machine should automatically start the backend monitoring worker/poller so the new machine begins collecting snapshots without a separate manual step.\n- The Monitoring tab should request all retained collector samples by default, while the dashboard overview can continue to use a shorter recent window.\n- Show CPU and RAM usage for the last hour.\n- Show IO wait percentage for the last hour.\n- On the dashboard overview, summarize monitoring metrics as 10-minute averages with high/low values for quick inspection.\n- Show average and spike/peak values for network throughput and disk I/O.\n- Show used, available, and total disk space for the configured media root, falling back to `/`.\n- The dashboard should present disk space as a single combined card with the progress/fill bar embedded inside the card and the size breakdown laid out clearly, with centered sub-card text for the Used/Free/Total breakdown and consistent vertical spacing across the dashboard cards.\n- The disk usage bar should change color as usage increases so high utilization is easy to notice at a glance.\n- The disk usage card should avoid redundant percentage labels next to the bar if the bar itself already communicates the value.\n- Render Monitoring charts directly with D3 so the UI can support brush-based range selection, hover tooltips with a moving vertical cursor and snapped point markers, summary chips, and moving averages without a separate wrapper library.\n- Use a lightweight remote collector that reads Linux `/proc`, `/sys/block`, and `df` data into a JSONL file under `/tmp`.\n- The collector should rotate/prune its JSONL metrics file so it does not grow unbounded; default retention is 7 days with a 70,000-line safety cap.\n- The collector should be startable/stoppable/restartable from the dashboard and should not require installing a full monitoring stack.\n- Machine definitions for monitoring should persist in backend-owned storage so local and remote monitoring targets survive restarts.\n- Monitoring machine actions/history should also persist in backend-owned storage so each machine section can show recent status/metrics/disk/collector activity.\n- The backend should periodically poll defined monitoring machines itself; no remote agent or push model should be required.\n- Last-hour charts require the collector to have been running long enough to collect samples.\n- Because the collector keeps only a bounded history, the Monitoring tab can safely load all retained samples up to the retention/max-lines cap.\n- Network throughput should be shown as a combined traffic chart with download and upload lines.\n- Network throughput should use bytes-per-second display units such as KB/s, MB/s, and GB/s to avoid bit/byte ambiguity.\n- Disk throughput should be shown as a combined I/O chart with read and write lines.\n- Network and disk throughput charts should scale values into readable units such as KB/s, MB/s, and GB/s.\n- Each Monitoring chart should show compact summary chips such as min/avg/max for quick inspection.\n- The Monitoring toolbar should offer quick time-range buttons such as 1h, 8h, 1 day, and 7 days in addition to free brush selection.\n- The Monitoring brush selection should persist across data refreshes and tab reloads instead of resetting whenever new samples arrive, and the zoom buttons should stay in sync with the visible brush range.\n- The Monitoring brush UI should be stable and remain visible after drags or zoom changes; it should be rendered independently from the chart redraw cycle, with usable resize handles for left/right edges.\n\n### Saved Actions / Remote Jobs\n\n- Provide an Actions tab for predefined server tasks that users can save and run later.\n- The initial task types should support shell commands and Python scripts, while keeping the design flexible for future task types.\n- Avoid arbitrary free-form command execution for ad-hoc execution; tasks should be stored records with an explicit name, type, content, enabled flag, default machine, and notes.\n- Support running tasks against either the local API host or a configured SSH machine using the same machine registry used by Monitoring.\n- Command/script content should be executed through the existing safe process helpers and shell-quoted where applicable.\n- Future destructive actions should require explicit confirmations or dry-run style safeguards.\n- Job templates should remain centralized in `jobs.py` for future extension.\n- Remote job template values must be shell-quoted before execution.\n\n## Decision Log\n\n- 2026-05-03: Reaffirmed that the Monitoring tab charts should be rendered directly with D3 and expose brush-based time-range selection plus moving averages.\n- 2026-05-03: Added hover tooltips, summary chips, a moving vertical cursor, snapped point markers, and a selected-range label to the D3 Monitoring charts for faster visual inspection.\n- 2026-05-03: Combined network download/upload into one traffic chart and disk read/write into one I/O chart for clearer Monitoring layout.\n- 2026-05-03: Confirmed the shared session activity table should keep the session identifier as a caption under the user name instead of a full column.\n- 2026-05-03: Confirmed the shared session table should keep the compact overall status summary line above the rows.\n- 2026-05-03: Updated the dashboard monitoring cards to show 10-minute averages with high/low subtext instead of only the latest sample.\n- 2026-05-03: Added OIDC/JWT auth support plus root-level Docker Compose deployment files for production and dev workflows.\n- 2026-05-04: Backend Docker Compose now mounts a host SSH directory into `/root/.ssh` so Paramiko can use a private key and strict host-key checking without baking secrets into the image.\n- 2026-05-04: The frontend was adjusted to be more mobile-safe by making the app shell tabs scrollable, stacking header controls on narrow screens, and hiding low-priority table columns on smaller displays.\n- 2026-05-04: The dashboard disk space area was consolidated into a single combined card with the progress bar embedded inside the card and the space stats reorganized into clearer, centered sub-panels with consistent vertical spacing.\n- 2026-05-04: The disk usage bar was color-coded to shift from green to yellow to red as utilization increases, and the redundant percentage label beside the bar was removed.\n- 2026-05-04: The Monitoring brush now persists its selected range in browser storage, avoids resetting when fresh monitoring data streams in, keeps the zoom buttons synchronized with the brush state, and renders the brush UI independently so data refreshes do not make it disappear.\n- 2026-05-04: The Monitoring brush was rebuilt as a React overlay with explicit resize handles so mouse dragging is more reliable.\n- 2026-05-04: The Media tab now persists search/filter/sort/pagination state, and the File Browser now persists the current directory plus selected file across reloads and tab switches.\n- 2026-05-04: The app header was upgraded to a two-row branded layout with a left logo mark, right-side username/logout controls, and a separate navigation row.\n- 2026-05-04: Frontend OIDC storage was switched from session-only defaults to localStorage-backed user/state stores so reloads keep the auth flow intact.\n- 2026-05-04: API requests now fall back to the persisted OIDC user store for the bearer token so the first render after reload can avoid spurious 401s.\n- 2026-05-04: Oversized frontend/backend modules were split into thin re-export entrypoints plus implementation modules to keep page/router/service code maintainable without changing behavior.\n- 2026-05-04: The Monitoring charts and File Browser were also split into implementation modules behind thin entrypoints so the larger UI surfaces stay easier to navigate without changing runtime behavior.\n- 2026-05-06: Monitoring became machine-based: a Settings tab now persists local/remote machine definitions, and the Monitoring tab renders a section per configured machine so API-host and remote targets are handled through the same UI model.\n- 2026-05-06: Compose files were switched away from `env_file` and now rely on environment-variable interpolation, so deployments can be driven entirely by shell exports or inline environment values.\n- 2026-05-06: Monitoring endpoints now translate machine-specific transport/runtime failures into user-facing HTTP errors so a broken machine only affects its own section instead of taking down the whole Monitoring page.\n- 2026-05-06: Documentation now includes explicit Compose interpolation examples plus a monitoring-machine configuration workflow showing how to add local and SSH machines in the Settings tab.\n- 2026-05-06: Monitoring machine action history was added so each machine section can display recent operation results, durations, and failures alongside the charts.\n- 2026-05-06: Monitoring history collection was shifted to a backend-scheduled poller that reads the defined machines over SSH/local shell and stores snapshots in SQLite, avoiding any remote agent or push requirement.\n- 2026-05-06: The dashboard monitoring section was converted from summary cards into a table of all configured machines, paired with backend poller status so the whole fleet can be reviewed at a glance.\n- 2026-05-06: The dashboard monitoring table now shows 10-minute averages with min/max subtext and can be sorted by machine, status, and metric columns.\n- 2026-05-07: The dashboard monitoring table should render each metric as a two-tier cell with a dominant centered 10-minute average in the upper area and a smaller lower area for min/max chips so the average is the visual focus.\n- 2026-05-07: The lower min/max area in each monitoring metric cell should span the full width of the cell, and table/header spacing should stay tuned for a dense admin-console layout.\n- 2026-05-07: The monitoring metric average should be explicitly labeled as a 10m average in the cell so the summary value is not ambiguous.\n- 2026-05-07: The monitoring overview's Updated column should use a compact fixed timestamp format instead of locale-specific output for easier scanning, and the cell should show two stacked lines: a readable month/day timestamp and a compact clock-plus-age line.\n- 2026-05-07: The monitoring overview table should allow horizontal scrolling when the dense column set exceeds the viewport width.\n- 2026-05-07: The monitoring overview table was rewritten so the machine, mode, status, metric, and updated columns each have explicit widths and the metric cells use a clean two-tier layout with full-width min/max rows.\n- 2026-05-07: Monitoring collector status should show a simple running/not-running state in the UI rather than surfacing backend process IDs.\n- 2026-05-07: Creating or updating a monitoring machine should kick the poller immediately so the backend starts collecting snapshots right away instead of waiting for the next interval.\n- 2026-05-07: The monitoring overview machine cell should reserve more room for the machine name and mode columns so the name does not overlap the mode text.\n- 2026-05-06: The Monitoring page now includes a poller-health badge in the header so users can quickly see whether backend collection is active.\n- 2026-05-06: The dashboard monitoring table now renders each metric summary with compact stacked low/high lines to keep the table narrower, and the activity/session table no longer hides columns on mobile so all details remain available.\n- 2026-05-06: The dashboard monitoring table now renders the 10-minute value as the visual focus and keeps the low/high lines smaller as supporting detail.\n- 2026-05-06: The file browser and media routes now support machine-specific SSH/Jellyfin selection via the request machine id, and the backend resolves clients from configured machines before falling back to legacy env-based defaults.\n- 2026-05-06: Application settings now include per-machine Jellyfin/Jellyseerr configuration and multi-select service roles so the UI can manage app hosts from the same machine registry.\n- 2026-05-06: The app shell now uses an Applications top-level tab with a Jellyfin subtab for media/library work and a placeholder Nextcloud subtab for future expansion.\n- 2026-05-06: The settings model now treats Jellyfin/Jellyseerr as machine-level configuration instead of global env-only values, so app hosts can be edited alongside other machine services.\n- 2026-05-06: The backend now synthesizes a managed `known_hosts` file from configured SSH machines at startup, avoiding a mounted SSH directory while keeping strict host-key verification enabled.\n- 2026-05-06: SSH credentials were moved toward reusable saved key records in Settings, so machines can point at a shared SSH key instead of storing their own duplicate private key text.\n- 2026-05-06: The Settings page now includes an SSH key registry UI with create/edit/delete flows and a generate-key action so users can make a reusable key directly in the web interface.\n- 2026-05-06: Saved SSH keys now surface a derived public key, fingerprint, and per-key machine usage count in the Settings UI for easier auditing.\n- 2026-05-06: The dev Compose stack now starts without any SSH key material at all unless a user later configures remote SSH machines.\n- 2026-05-06: The Settings page now exposes a protected local-database reset flow that requires several explicit acknowledgements and a typed confirmation phrase before it can delete the cached app databases.\n- 2026-05-06: The Actions page was redesigned into a compact tabbed workspace with a left tab rail of saved actions, and both new-action creation and editing now open in popups instead of inline forms.\n- 2026-05-07: Added a reusable dashboard shortcuts container with persisted records so the dashboard can link to external websites now and later support action/user shortcut types from the same model.\n- 2026-05-07: Dashboard shortcuts gained an optional icon/preview field so cards can be visually differentiated while keeping future shortcut types extensible.\n- 2026-05-07: The dashboard shortcut editor was tightened with compact type guidance and shorter helper text so the popup stays readable without wasting vertical space.\n- 2026-05-07: SSH key records should persist and display the derived public key and fingerprint, not just the private key blob, so imports and generated keys are auditable without recomputation.\n- 2026-05-07: SSH machine creation/editing should present a saved-key dropdown and warn when no SSH keys exist yet, instead of forcing manual key-id entry.\n- 2026-05-07: Saved task runs should return structured failure output for local execution problems instead of surfacing a generic 500 error.\n- 2026-05-07: SSH dependency resolution should keep its cached tuple shape aligned with the legacy and machine-specific SSH settings so SSH clients can be created without tuple-unpack crashes.\n- 2026-05-07: Machine creation was adjusted so dialog edits are controlled by the parent form state, ensuring all entered fields are actually saved.\n- 2026-05-07: Local machines now work through the same Files/Jobs/monitoring tool paths without SSH credentials, and creating a machine starts the monitoring worker automatically.\n- 2026-05-06: Closing an edited Action popup now warns before discarding unsaved changes.\n- 2026-05-06: The Settings page was reworked into a compact tabbed layout with separate Machines, SSH Keys, and Danger Zone sections, and irrelevant machine/service fields now hide when that mode or service is not selected.\n- 2026-05-06: The Settings machine list was converted into a compact table with popup editing so the page no longer repeats the full machine form for every entry.\n- 2026-05-06: The Monitoring page was reworked into an overview tab plus one tab per configured machine, so each machine gets its own monitoring page.\n- 2026-05-06: The Actions page now shows a visible empty-state container when no action is selected, and the Monitoring tabs were returned to the app's standard tab styling.\n- 2026-05-06: The Monitoring tab spacing was tightened and the Applications page was updated to match the app's standard card-and-tab design.\n- 2026-05-06: The dashboard library stats were moved into the Jellyfin Applications tab above the media table, and the dashboard activity panel was relabeled as Jellyfin activity.\n- 2026-05-06: The dashboard, Monitoring overview, Applications, and File Browser layouts were normalized around shared titled containers, with Monitoring showing the fleet overview before machine tabs and File Browser using machine tabs with Browser/Media info/Jobs stacked in each machine view.\n- 2026-05-06: The Applications and Monitoring tab containers were refactored into a reusable bordered tab-card pattern so the active tab's content sits inside the same card as the tab rail.\n- 2026-05-06: The Settings Machines tab was changed from a repeated form layout to a left-hand machine list with a right-hand details panel and popup editing.\n- 2026-05-06: The app no longer seeds a default local monitoring machine; a fresh database starts with an empty machine list until the user creates one.\n- 2026-05-06: The File Browser was reworked into Browser / Media info / Jobs subtabs.\n- 2026-05-06: The app shell received a small density pass that tightened container padding and tab widths to make the whole site feel more compact.\n- 2026-05-06: The tab rails across Actions, Monitoring, Settings, and Files were restyled to be more enterprise-console-like with compact pills, clearer active states, and reduced visual noise.\n- 2026-05-06: Added an Actions tab for saved server tasks, with backend persistence, per-task run history, and support for shell/Python task types on either local or SSH machines.\n- 2026-05-06: Reusable dialog footers now keep cancel on the left and confirm on the right, and hover edit buttons now appear on the right edge of editable list rows in Actions and Settings.\n- 2026-05-06: Library stats, Jellyfin activity, and Monitoring overview now use shared section-container patterns so subcontainers stay consistent across the app.\n- 2026-05-07: The app versioning scheme should be hybrid: auto-detect package/build metadata when available, but allow explicit overrides for deployments that need fixed labels.\n- 2026-05-07: The shell should display both frontend and backend version labels so deployed builds are easy to identify without opening a separate diagnostics screen.\n- 2026-05-07: SSH host verification should use trust-on-first-use for new machines by recording the first observed host key into the backend-managed known_hosts file, while still rejecting later key mismatches.\n- 2026-05-07: The SSH machine editor should expose a validation button that tests banner/auth flow and records the host key before save so users get clear feedback when a host is unreachable.\n- 2026-05-07: Saving a monitoring-capable machine should validate the banner/auth flow, update the backend-managed known_hosts entry for the current host, and start the remote resource collector so charts populate without a separate manual step.\n- 2026-05-07: Machine settings should visually separate Connection, Monitoring / Files, Jellyfin, Jellyseerr, and Notes into clearly labeled sections.","version":1,"previousVersionId":null,"category":"spec","status":"active","immutable":false} -{"id":"el-1fr","type":"document","createdAt":"2026-05-11T19:27:44.929Z","updatedAt":"2026-05-11T19:27:44.929Z","createdBy":"el-2rt3","tags":[],"metadata":{},"title":"Migration Plan: Streamlit to FastAPI + React","contentType":"markdown","content":"# Migration Plan: Streamlit → FastAPI + React\n\n## Overview\n\nSplit the current Streamlit monolith into:\n\n- **Backend:** FastAPI (Python) serving a REST API\n- **Frontend:** React SPA (TypeScript) consuming that API\n\nThe existing `clients/`, `domain/`, `services/`, `jobs.py`, `utils.py`, and `config.py` are already UI-independent and transfer directly to the FastAPI backend with minimal changes.\n\n---\n\n## Current Architecture\n\n```\nsrc/media_library_viewer/\n├── app.py # Streamlit orchestration + caching (DELETE)\n├── config.py # Env/dotenv config\n├── jobs.py # SSH job templates\n├── utils.py # Formatting helpers\n├── clients/\n│ ├── jellyfin.py # Jellyfin HTTP client\n│ ├── resources.py # Remote resource collector\n│ └── ssh.py # SSH command execution\n├── domain/\n│ └── media.py # Media normalization\n├── services/\n│ └── media_index.py # SQLite media index\n└── ui/ # Streamlit rendering (DELETE)\n ├── dashboard.py\n ├── file_browser.py\n ├── media.py\n └── preview.py\n```\n\n---\n\n## Target Architecture\n\n```\nrepo/\n├── backend/\n│ ├── main.py # FastAPI app + CORS + lifespan\n│ ├── config.py # pydantic-settings based config\n│ ├── dependencies.py # DI for SSH/Jellyfin clients\n│ ├── routers/\n│ │ ├── dashboard.py # /api/dashboard/*\n│ │ ├── monitoring.py # /api/monitoring/*\n│ │ ├── media.py # /api/media/*\n│ │ ├── files.py # /api/files/*\n│ │ └── jobs.py # /api/jobs/*\n│ ├── clients/ # Copied unchanged\n│ │ ├── jellyfin.py\n│ │ ├── resources.py\n│ │ └── ssh.py\n│ ├── domain/ # Copied unchanged\n│ │ └── media.py\n│ ├── services/ # Copied unchanged\n│ │ └── media_index.py\n│ ├── jobs.py # Copied unchanged\n│ ├── utils.py # Copied unchanged\n│ └── pyproject.toml\n│\n├── frontend/\n│ ├── package.json\n│ ├── vite.config.ts\n│ ├── tsconfig.json\n│ └── src/\n│ ├── main.tsx\n│ ├── App.tsx\n│ ├── api/\n│ │ └── client.ts # Typed fetch wrappers\n│ ├── hooks/\n│ │ ├── useDashboard.ts\n│ │ ├── useMonitoring.ts\n│ │ ├── useMedia.ts\n│ │ └── useFiles.ts\n│ ├── pages/\n│ │ ├── Dashboard.tsx\n│ │ ├── Monitoring.tsx\n│ │ ├── Media.tsx\n│ │ └── FileBrowser.tsx\n│ ├── components/\n│ │ ├── NowPlaying.tsx\n│ │ ├── MetricCard.tsx\n│ │ ├── LibraryOverview.tsx\n│ │ ├── MonitoringCharts.tsx\n│ │ ├── MediaTable.tsx\n│ │ ├── FileListing.tsx\n│ │ └── FfprobePreview.tsx\n│ └── types/\n│ └── index.ts\n│\n├── docker-compose.yml # Optional unified deployment\n└── README.md\n```\n\n---\n\n## FastAPI Endpoints\n\n| Endpoint | Method | Source | Description |\n| ------------------------------- | ------ | ---------------------------------------- | -------------------------------- |\n| `/api/dashboard/counts` | GET | `jellyfin.media_counts()` | Movie/series/episode totals |\n| `/api/dashboard/libraries` | GET | `jellyfin.library_item_counts()` | Per-library breakdown |\n| `/api/dashboard/now-playing` | GET | `jellyfin.active_sessions()` | Active sessions + transcode info |\n| `/api/monitoring/status` | GET | `resources.resource_collector_status()` | Collector running? |\n| `/api/monitoring/metrics` | GET | `resources.read_resource_metrics()` | Last-hour JSONL samples |\n| `/api/monitoring/disk` | GET | `resources.disk_space()` | df for media root |\n| `/api/monitoring/start` | POST | `resources.start_resource_collector()` | Start collector |\n| `/api/monitoring/stop` | POST | `resources.stop_resource_collector()` | Stop collector |\n| `/api/monitoring/restart` | POST | `resources.restart_resource_collector()` | Restart collector |\n| `/api/media/status` | GET | `MediaIndex.status()` | Index exists/count/age |\n| `/api/media/build` | POST | `build_media_index()` | Rebuild index |\n| `/api/media/query` | GET | `MediaIndex.query()` | Filtered/sorted/paged query |\n| `/api/files/list?path=` | GET | `ssh.list_dir()` | Directory listing |\n| `/api/files/ffprobe?path=` | GET | `ssh.ffprobe_json()` | ffprobe JSON |\n| `/api/files/stat?path=` | GET | `ssh.stat_path()` | stat output |\n| `/api/files/resolve-path?path=` | GET | `resolve_remote_media_path()` | Jellyfin→SSH path mapping |\n| `/api/jobs/templates` | GET | `JOB_TEMPLATES` | Available job list |\n| `/api/jobs/run` | POST | `run_job()` | Execute a job |\n\n---\n\n## React Frontend\n\n### Tech Stack\n\n- **Vite** + React 18+ + TypeScript\n- **@tanstack/react-query** — data fetching with polling\n- **ag-grid-react** — media table and file browser (same grid lib)\n- **recharts** — monitoring line charts\n- **react-router** — page navigation\n- **tailwindcss** + **shadcn/ui** — styling\n\n### Page → Component Mapping\n\n| Page | Components | Polling |\n| ------------ | --------------------------------------------------------- | -------------------------------- |\n| Dashboard | NowPlaying, MetricCard (server overview), LibraryOverview | 15s (now-playing), 30s (metrics) |\n| Monitoring | MonitoringCharts, MetricCard, CollectorControls | 15s |\n| Media | MediaTable (AG Grid), filter/sort/page controls | on-demand |\n| File Browser | FileListing (AG Grid), FfprobePreview, JobRunner | on-demand |\n\n### Key Interactions\n\n- Media row click → updates client-side file browser path state (no API call)\n- File browser directory click → fetches `/api/files/list?path=...`\n- File browser file click → fetches `/api/files/ffprobe?path=...`\n- Path input Enter → navigates directory\n- Monitoring charts → recharts line chart from `/api/monitoring/metrics`\n\n---\n\n## What Transfers Unchanged (~1,350 lines)\n\n| File | Lines | Notes |\n| ------------------------- | ----- | ---------------------------- |\n| `clients/jellyfin.py` | 167 | Remove unused methods if any |\n| `clients/ssh.py` | 146 | No changes |\n| `clients/resources.py` | 316 | No changes |\n| `domain/media.py` | 161 | No changes |\n| `services/media_index.py` | 278 | No changes |\n| `jobs.py` | 59 | No changes |\n| `utils.py` | 227 | No changes |\n\n---\n\n## What Gets Deleted\n\n- `src/media_library_viewer/ui/` (all Streamlit rendering)\n- `src/media_library_viewer/app.py` (Streamlit orchestration)\n- Root `app.py` (Streamlit launcher)\n- `streamlit` and `streamlit-aggrid` dependencies\n\n---\n\n## Migration Steps\n\n### Step 1: Backend scaffold\n\n- Create `backend/` with FastAPI, pydantic-settings config, DI\n- Copy `clients/`, `domain/`, `services/`, `jobs.py`, `utils.py`\n- Implement routers wrapping existing functions\n- Test with curl\n\n### Step 2: Frontend scaffold\n\n- Create `frontend/` with Vite + React + TypeScript\n- API client with typed wrappers\n- React Query provider + hooks\n- Router with 4 pages\n\n### Step 3: Build pages (one at a time)\n\n1. Dashboard (simplest — just fetches and displays)\n2. Monitoring (charts + controls)\n3. Media (AG Grid + filters)\n4. File Browser (AG Grid + ffprobe + jobs)\n\n### Step 4: Validate feature parity\n\n- Compare behavior side-by-side with Streamlit\n- Remove Streamlit code\n\n---\n\n## Effort Estimate\n\n| Component | Effort |\n| ------------------------------------- | ------------- |\n| FastAPI backend | 1-2 days |\n| React scaffold + routing + API client | 0.5 day |\n| Dashboard page | 0.5 day |\n| Monitoring page + charts | 1 day |\n| Media page + AG Grid | 1 day |\n| File browser page | 1 day |\n| Polish + testing + deployment | 1-2 days |\n| **Total** | **~6-9 days** |\n\n---\n\n## Key Decisions\n\n- **Polling over WebSocket initially** — simpler, matches current behavior; upgrade path exists for later.\n- **Auth deferred** — rely on network access control for now; add API key or JWT later.\n- **CORS enabled** — for Vite dev server and production origin.\n- **Path resolution stays server-side** — frontend sends Jellyfin paths, backend resolves to SSH paths.\n- **Both can coexist** — Streamlit and FastAPI can run simultaneously during transition since they share the same clients/services.\n\n---\n\n## Optional Post-Migration Enhancements\n\n- WebSocket push for real-time monitoring/now-playing\n- JWT authentication\n- Background index build with SSE progress\n- Dark/light theme\n- Persistent user preferences (localStorage + optional backend sync)\n- Docker Compose (backend + frontend + nginx reverse proxy)","version":1,"previousVersionId":null,"category":"spec","status":"active","immutable":false} -{"id":"el-580","type":"document","createdAt":"2026-05-11T19:27:45.965Z","updatedAt":"2026-05-11T19:27:45.965Z","createdBy":"el-2rt3","tags":[],"metadata":{},"title":"Contributing Guide","contentType":"markdown","content":"# Contributing\n\nThanks for considering a contribution.\n\n## Setup\n\n```bash\npython -m venv .venv\nsource .venv/bin/activate\npip install -e '.[dev]'\n```\n\nCopy env template:\n\n```bash\ncp .env.example .env\n```\n\nThen set real values in `.env` and run:\n\n```bash\nstreamlit run app.py\n```\n\n## Development guidelines\n\n- Keep architecture boundaries clear:\n - `clients/` for external integrations\n - `domain/` for normalization/business logic\n - `services/` for app services/indexing\n - `ui/` for Streamlit rendering\n- Avoid introducing optional fallback paths unless required.\n- Prefer small, focused functions and explicit session-state keys.\n- Preserve safe SSH behavior and path quoting.\n\n## Validation\n\nBefore opening a merge request, run:\n\n```bash\nPYTHONPATH=src python -m py_compile app.py src/media_library_viewer/*.py src/media_library_viewer/clients/*.py src/media_library_viewer/domain/*.py src/media_library_viewer/services/*.py src/media_library_viewer/ui/*.py\n```\n\n## Security / secrets\n\nNever commit:\n\n- `.env`\n- `.streamlit/secrets.toml`\n- private keys or API tokens\n\nUse `.env.example` for documented placeholders only.\n\n## Pull requests\n\nPlease include:\n\n- what changed\n- why it changed\n- how it was tested\n\nIf behavior/requirements changed, also update `docs/REQUIREMENTS.md`.","version":1,"previousVersionId":null,"category":"how-to","status":"active","immutable":false} -{"id":"el-3ub","type":"document","createdAt":"2026-05-11T19:27:46.826Z","updatedAt":"2026-05-11T19:27:46.826Z","createdBy":"el-2rt3","tags":[],"metadata":{},"title":"Backend API Reference","contentType":"markdown","content":"# Manage Backend API\n\nFastAPI backend serving the REST API for Jellyfin media browsing, SSH file inspection, server monitoring, and JWT-protected access for Manage.\n\n## Project structure\n\n```\nbackend/\n├── pyproject.toml\n├── README.md\n├── src/\n│ └── media_library_viewer_api/\n│ ├── __init__.py\n│ ├── main.py # FastAPI app entrypoint\n│ ├── config.py # pydantic-settings config\n│ ├── dependencies.py # Dependency injection\n│ ├── path_utils.py # Jellyfin→SSH path resolution\n│ ├── jobs.py # Job templates\n│ ├── utils.py # Formatting helpers\n│ ├── routers/\n│ │ ├── dashboard.py\n│ │ ├── monitoring.py\n│ │ ├── media.py\n│ │ ├── users.py\n│ │ ├── settings.py\n│ │ ├── files.py\n│ │ └── jobs.py\n│ ├── clients/\n│ │ ├── jellyfin.py\n│ │ ├── jellyseerr.py\n│ │ ├── local.py\n│ │ ├── resources.py\n│ │ └── ssh.py\n│ ├── domain/\n│ │ └── media.py\n│ └── services/\n│ ├── media_index.py\n│ └── settings_store.py\n└── tests/\n```\n\n## Setup\n\n```bash\ncd backend\npython -m venv .venv\nsource .venv/bin/activate\npip install -e '.[dev]'\n```\n\n## Configuration\n\nSet environment variables directly in your shell or a wrapper script before running the app or Compose. The Docker Compose files use interpolation and do not require an `env_file` entry.\n\nFor local `.env` development, you can still create one if you prefer, but it is optional.\n\n```bash\n# Optional backend logging level\nLOG_LEVEL=INFO\n\n# Optional SMTP settings for the Users -> message popup\nSMTP_HOST=smtp.example.com\nSMTP_PORT=587\nSMTP_USERNAME=your-smtp-username\nSMTP_PASSWORD=your-smtp-password\nSMTP_FROM_ADDRESS=no-reply@example.com\nSMTP_FROM_NAME=Manage\nSMTP_USE_TLS=true\nSMTP_USE_SSL=false\nSMTP_TIMEOUT=30\n\n# Jellyfin, Jellyseerr, and SSH targets are now configured per machine in the app's Settings tab.\n# The backend seeds a local machine automatically, so no global Jellyfin or SSH env vars are required.\n\n# Versioning\n# The backend tries to auto-detect its version from installed package metadata.\n# If needed, you can override the displayed version/build markers with APP_VERSION and APP_BUILD_INFO.\nAPP_VERSION=0.1.0\nAPP_BUILD_INFO=dev\n\n# Authentik / OIDC\nAUTH_ENABLED=true\nOIDC_ISSUER_URL=https://authentik.example/application/o/media-library-viewer/\nOIDC_AUDIENCE=media-library-viewer\nOIDC_JWKS_URL=\nOIDC_CLOCK_SKEW_SECONDS=30\n```\n\n## Running\n\n```bash\ncd backend\nuvicorn media_library_viewer_api.main:app --reload --port 8000\n```\n\nOr with PYTHONPATH if not installed:\n\n```bash\nPYTHONPATH=src uvicorn media_library_viewer_api.main:app --reload --port 8000\n```\n\nAPI docs available at: http://localhost:8000/docs\n\n## Docker\n\nThe repository root includes a production `docker-compose.yml` and a development `docker-compose.dev.yml`.\nThe backend media index is stored in the `backend_cache` Docker volume so it survives container restarts and image rebuilds.\nThe production compose file expects required environment variables to be supplied via interpolation (shell exports or inline `VAR=value docker compose ...`).\n\n### Configuration workflow examples\n\n1. Export your runtime variables before launching Compose:\n\n```bash\nexport BACKEND_APP_HOST=manage.example.com\nexport FRONTEND_APP_HOST=manage.example.com\nexport CERT_RESOLVER=letsencrypt\nexport VITE_OIDC_ISSUER=https://authentik.example/application/o/manage/\nexport VITE_OIDC_CLIENT_ID=manage\nexport VITE_OIDC_REDIRECT_URI=https://manage.example.com/\nexport VITE_OIDC_POST_LOGOUT_REDIRECT_URI=https://manage.example.com/\n\ndocker compose up --build\n```\n\n2. After the API is running, open the app, go to **Settings**, and add machine entries:\n - **Local**: monitors the API host itself without SSH.\n - **SSH**: monitors another machine using a host, username, and a private key pasted directly into the machine settings, with an optional passphrase.\n - The machine editor groups Connection, Monitoring / Files, Jellyfin, Jellyseerr, and Notes under separate headings so each service area is easier to scan.\n - Saving a monitoring machine now validates the banner/auth flow, records the first trusted host key into the backend-managed `known_hosts` file, and starts the collector so charts populate without a separate manual step.\n - If the host cannot be reached or authenticated, the save flow surfaces the SSH error directly in the dialog.\n - Use **Validate SSH + trust host** in the machine editor before saving if you want to test the banner/auth flow explicitly.\n - The first successful SSH connection uses trust-on-first-use: the backend records that machine's host key into its managed `known_hosts` file automatically, then continues verifying it strictly on later connects.\n\n3. Open **Monitoring** to see one section per configured machine. Each section uses its own collector state, disk path, metrics queries, and recent action history, which are populated automatically by the backend poller.\n\nFor local development, `docker compose -f docker-compose.dev.yml up --build` does not require an SSH key unless you configure remote SSH machines in the Settings tab.\n\n## API Endpoints\n\n- `GET /api/dashboard/counts` — Movie/series/episode totals\n- `GET /api/dashboard/libraries` — Per-library breakdown\n- `GET /api/dashboard/now-playing` — Active playback sessions\n- `GET /api/monitoring/machines` — Persistent monitoring machine definitions\n- `GET /api/monitoring/status?machine_id=` — Collector status for a machine\n- `GET /api/monitoring/metrics?machine_id=` — Resource samples (last hour)\n- `GET /api/monitoring/disk?machine_id=` — Disk space\n- `POST /api/monitoring/start|stop|restart?machine_id=` — Collector controls\n- `GET /api/monitoring/diagnostics?machine_id=` — Collector debug info\n- `GET /api/monitoring/poller` — Backend poller status and configuration\n- `GET /api/monitoring/machines/{machine_id}/actions` — Recent machine action history\n- `GET /api/dashboard/monitoring` — Dashboard-wide per-machine monitoring summary table with 10-minute averages and min/max subtext\n- `GET /api/settings/machines` — Manage machine definitions\n- `GET /api/media/status` — Index status\n- `POST /api/media/build` — Rebuild index\n- `GET /api/media/query` — Query with filters/sort/pagination\n- `GET /api/files/list?path=` — Directory listing\n- `GET /api/files/ffprobe?path=` — ffprobe JSON\n- `GET /api/files/stat?path=` — stat output\n- `GET /api/files/resolve-path?path=` — Path resolution\n- `GET /api/jobs/templates` — Available jobs\n- `POST /api/jobs/run` — Execute a job\n- `GET /api/users` — Jellyfin users with optional Jellyseerr enrichment\n- `GET /api/version` — Backend version/build metadata for the UI","version":1,"previousVersionId":null,"category":"reference","status":"active","immutable":false} -{"id":"el-2oe","type":"document","createdAt":"2026-05-11T19:27:47.668Z","updatedAt":"2026-05-11T19:27:47.668Z","createdBy":"el-2rt3","tags":[],"metadata":{},"title":"Frontend Reference","contentType":"markdown","content":"# Manage Frontend\n\nReact + TypeScript SPA for Manage, consuming the FastAPI backend.\n\n## Tech Stack\n\n- **Vite** — Build tool\n- **React 18+** — UI framework\n- **TypeScript** — Type safety\n- **@tanstack/react-query** — Data fetching/caching\n- **ag-grid-react** — Data tables (media, file browser)\n- **recharts** — Monitoring charts\n- **react-router-dom** — Client-side routing\n- **Tailwind CSS** — Styling\n\n## Setup\n\n```bash\ncd frontend\nnpm install\n```\n\n## Development\n\n```bash\nnpm run dev\n```\n\nRuns on http://localhost:5173 with API requests proxied to http://localhost:8000.\n\nMake sure the backend is running:\n\n```bash\ncd ../backend\nuvicorn main:app --reload --port 8000\n```\n\n## Build\n\n```bash\nnpm run build\n```\n\nOutput goes to `frontend/dist/`.\n\n## Pages\n\n- **Dashboard** (`/`) — Now playing, backend-collected per-machine monitoring table with 10-minute averages/min/max, library stats, frontend/backend version chips in the shell header\n- **Monitoring** (`/monitoring`) — Per-machine CPU/IO wait/RAM/network/disk charts, collector controls, and backend-collected recent action history\n- **Media** (`/media`) — Full-library table with sort/filter/search\n- **Users** (`/users`) — Read-only Jellyfin user list with optional Jellyseerr enrichment\n- **File Browser** (`/files`) — Remote directory browsing, ffprobe preview, jobs\n- **Settings** (`/settings`) — Persistent monitoring machine definitions and setup workflow\n\n## Environment Variables\n\nSet `VITE_API_URL` and any OIDC variables directly in your shell or Compose build args if the API is not at `http://localhost:8000`.\nThe frontend version defaults to the package.json version and can be overridden with `VITE_APP_VERSION` and `VITE_APP_BUILD_INFO` when you need explicit deployed labels.\n\n```bash\nVITE_API_URL=http://your-backend-host:8000\n```\n\n## Configuration workflow examples\n\n- **Local development**: run `docker compose -f docker-compose.dev.yml up --build`, then open the app and add monitoring machines in the **Settings** tab.\n- **Production**: export the required Compose variables in your shell, run `docker compose up --build`, and manage local/remote machines from **Settings**.\n- **Dashboard monitoring UI**: the dashboard shows a compact, sortable table with one row per configured machine plus poller status.\n- **Monitoring UI**: the **Monitoring** tab shows one card per configured machine, including a recent action-history table populated by the backend poller. A machine can be `local` (the API host itself) or `ssh` (a remote host), and the UI treats both the same after configuration. The page also shows the backend poller health badge.\n\nIn development, the Vite proxy handles `/api` requests automatically.","version":1,"previousVersionId":null,"category":"reference","status":"active","immutable":false} diff --git a/.superpowers/brainstorm/435525-1778528804/content/overview.html b/.superpowers/brainstorm/435525-1778528804/content/overview.html deleted file mode 100644 index c5dfe07..0000000 --- a/.superpowers/brainstorm/435525-1778528804/content/overview.html +++ /dev/null @@ -1,50 +0,0 @@ -

UI Redesign Exploration

-

Let's figure out the direction for your complete UI redesign

- -
-

Current App Overview

-

Manage is a media operations dashboard with these main areas:

-
    -
  • Dashboard — Jellyfin activity, monitoring overview, shortcuts
  • -
  • Monitoring — Per-machine CPU/RAM/IO charts, collector controls
  • -
  • Applications — Jellyfin media library browsing
  • -
  • Users — Jellyfin user list with Jellyseerr enrichment
  • -
  • Actions — Saved server tasks and job execution
  • -
  • Files — Remote SSH file browser with ffprobe
  • -
  • Settings — Machine definitions, SSH keys, configuration
  • -
-
- -
-

Current Design Characteristics

-
    -
  • Material UI (MUI) with custom theme
  • -
  • Two-row header with logo + navigation tabs
  • -
  • Card-based layout with section containers
  • -
  • Dark mode support (auto-detects system preference)
  • -
  • Compact admin-console styling
  • -
  • AG Grid for data tables
  • -
  • D3 charts for monitoring
  • -
-
- -
-
Current App Shell
-
-
Logo "Manage" | version chips | user | logout
-
-
Dashboard
-
Monitoring
-
Applications
-
Users
-
Actions
-
Files
-
Settings
-
-
- Page content area (cards, tables, charts) -
-
-
- -

Ready to start exploring redesign options. Answer the questions in your terminal!

diff --git a/.superpowers/brainstorm/435525-1778528804/content/visual-direction.html b/.superpowers/brainstorm/435525-1778528804/content/visual-direction.html deleted file mode 100644 index ad61b4f..0000000 --- a/.superpowers/brainstorm/435525-1778528804/content/visual-direction.html +++ /dev/null @@ -1,38 +0,0 @@ -

Visual Direction

-

What aesthetic feel do you want for the new design?

- -
-
-
A
-
-

Minimal & Clean

-

Lots of whitespace, subtle borders, muted colors, content-first. Think Linear, Notion, or modern SaaS dashboards.

-
-
- -
-
B
-
-

Bold & Expressive

-

Strong colors, distinct visual personality, custom illustrations or iconography. Think Vercel, Stripe, or gaming dashboards.

-
-
- -
-
C
-
-

Professional & Enterprise

-

Polished but conservative, clear hierarchy, familiar patterns. Think AWS console, Datadog, or enterprise admin tools.

-
-
- -
-
D
-
-

Dark & Dramatic

-

Deep dark backgrounds, neon accents, high contrast. Think cyberpunk, developer tools, or media center aesthetics.

-
-
-
- -

Click your preferred style in the browser, or tell me in the terminal if you want something else entirely.

diff --git a/.superpowers/brainstorm/435525-1778528804/content/waiting.html b/.superpowers/brainstorm/435525-1778528804/content/waiting.html deleted file mode 100644 index f92c257..0000000 --- a/.superpowers/brainstorm/435525-1778528804/content/waiting.html +++ /dev/null @@ -1,3 +0,0 @@ -
-

Continuing in terminal...

-
\ No newline at end of file diff --git a/.superpowers/brainstorm/435525-1778528804/state/server-info b/.superpowers/brainstorm/435525-1778528804/state/server-info deleted file mode 100644 index def7fe5..0000000 --- a/.superpowers/brainstorm/435525-1778528804/state/server-info +++ /dev/null @@ -1 +0,0 @@ -{"type":"server-started","port":52977,"host":"127.0.0.1","url_host":"localhost","url":"http://localhost:52977","screen_dir":"/home/alex/projects/media_library_viewer/.superpowers/brainstorm/435525-1778528804/content","state_dir":"/home/alex/projects/media_library_viewer/.superpowers/brainstorm/435525-1778528804/state"} diff --git a/.superpowers/brainstorm/435525-1778528804/state/server.pid b/.superpowers/brainstorm/435525-1778528804/state/server.pid deleted file mode 100644 index 3bb4551..0000000 --- a/.superpowers/brainstorm/435525-1778528804/state/server.pid +++ /dev/null @@ -1 +0,0 @@ -435537