From ff5f4e91337bfd161b8170eff71f8e2222a05a56 Mon Sep 17 00:00:00 2001 From: Samu Date: Fri, 4 Sep 2026 13:27:00 +0200 Subject: [PATCH] docs: navidrome deep-link implementation plan (3 tasks) --- .../plans/2026-09-04-navidrome-link.md | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 docs/superpowers/plans/2026-09-04-navidrome-link.md diff --git a/docs/superpowers/plans/2026-09-04-navidrome-link.md b/docs/superpowers/plans/2026-09-04-navidrome-link.md new file mode 100644 index 0000000..f8751e4 --- /dev/null +++ b/docs/superpowers/plans/2026-09-04-navidrome-link.md @@ -0,0 +1,105 @@ +# Navidrome Deep Link Implementation Plan (plan 4) + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the in-app album player with a server-built Navidrome album link on the item page. + +**Architecture:** Deletion-heavy change: the player (context, mini-bar, audio element), stream/tracks/played proxy routes, and their tests are removed. `matchedAlbum` in the item detail gains `webUrl`, built server-side from the user's Subsonic URL. Spec: `docs/superpowers/specs/2026-09-04-navidrome-link-design.md`. + +**Tech Stack:** unchanged. **Prerequisites:** main (post Wave 1), 205/205 tests. Branch: `navidrome-link`. + +--- + +### Task 1: Backend — webUrl in matchedAlbum; delete stream routes + +**Files:** +- Modify: `server/src/routes/collectionRoutes.ts`, `server/src/app.ts` +- Delete: `server/src/routes/streamRoutes.ts`, `server/test/stream.test.ts` +- Test: `server/test/collection.test.ts` (modify), `server/test/lastplayed.test.ts` (modify) + +- [ ] **Step 1: Modify the detail route in `server/src/routes/collectionRoutes.ts`** — build webUrl from the user's Subsonic URL (import `getSettings` from './settingsRoutes.js'): + +```ts + const matched = findMatchedAlbum(db, request.user.id, row.id) + const settings = getSettings(db, request.user.id) + const webUrl = + matched && settings.subsonic_url + ? `${settings.subsonic_url.replace(/\/+$/, '')}/app/#/album/${matched.subsonicId}` + : null + return { + ...rowToItem(db, row), + matchedAlbum: matched ? { ...matched, webUrl } : null, + loan: ... // unchanged + } +``` + +- [ ] **Step 2: Update the matchedAlbum test** in `server/test/collection.test.ts` ('detail includes matchedAlbum') — `webUrl` requires the user's subsonic_url: before the first detail assertion, PUT /api/settings with a subsonic config (reuse the ping-stub pattern from settings.test.ts so the PUT succeeds), seed album `alb-9`, then assert `matchedAlbum` matches `{ subsonicId: 'alb-9', webUrl: 'http://navidrome.local/app/#/album/alb-9' }`. Keep the pre-seed assertion (`matchedAlbum` null). + +- [ ] **Step 3: Delete the stream routes** — remove `server/src/routes/streamRoutes.ts`, `server/test/stream.test.ts`, and the import/registration lines in `server/src/app.ts`. Update `server/test/lastplayed.test.ts`: the 'played endpoint' test is removed (route deleted); keep the sync-stamping test (it exercises sync.ts, which stays). + +- [ ] **Step 4: Run tests** — `npm test && npm run typecheck` — expect ALL PASS (205 − 2 played/stream tests + 0 new = 203; exact count may shift ±1 with the lastplayed restructure — report it). + +- [ ] **Step 5: Commit** + +```bash +git add -A && git commit -m "feat: matchedAlbum.webUrl from subsonic settings; remove stream proxy routes" +``` + +--- + +### Task 2: Web — remove player, add the link + +**Files:** +- Modify: `web/src/pages/ItemPage.tsx`, `web/src/App.tsx`, `web/src/api.ts`, `web/src/types.ts`, `web/test/item.test.tsx`, `web/test/api.test.ts` +- Delete: `web/src/player/` (PlayerContext.tsx, MiniBar.tsx), `web/test/player.test.tsx`, `web/test/minibar.test.tsx` + +- [ ] **Step 1: Update failing tests first in `web/test/item.test.tsx`:** +- Remove PlayerProvider from `renderItem`, media prototype mocks, getAlbumTracks/markPlayed/lendItem mock entries where player-related; keep lendItem/returnLoan (loans stay) +- The rippedItem fixture keeps `matchedAlbum: matched` — extend `matched` with `webUrl: 'http://navidrome.local/app/#/album/alb-1'` +- Replace the 'shows Play…loads the player' test with: ripped+matched → link `[aria-label="Listen in Navidrome"]`... simpler: `getByRole('link', { name: /listen in navidrome/i })` with `getAttribute('href')` = the webUrl and `target` = '_blank' +- 'hides Play when unmatched or not ripped' → renamed: link absent when `matchedAlbum: null` (even when ripped) +- 'shows last played' unchanged +- lend/return tests unchanged + +- [ ] **Step 2: Implement `web/src/pages/ItemPage.tsx`:** remove `usePlayer` import + `load` usage + media code; replace the Play button block with: + +```tsx + {item.matchedAlbum && ( + + Listen in Navidrome ↗ + + )} +``` + +- [ ] **Step 3: Remove the player from the app:** delete `web/src/player/`, `web/test/player.test.tsx`, `web/test/minibar.test.tsx`; revert `web/src/App.tsx` wiring to `` (no PlayerProvider/MiniBar imports). + +- [ ] **Step 4: Remove dead api/types:** delete `getAlbumTracks`, `markPlayed`, `streamUrl` methods from `web/src/api.ts`; delete `Track`, `AlbumTracks` interfaces from `web/src/types.ts` AND add `webUrl: string` to the `MatchedAlbum` interface (the detail route now supplies it); remove their entries + the 'album tracks + played + stream url' test from `web/test/api.test.ts`. + +- [ ] **Step 5: Run tests** — `npm test && npm run typecheck` — expect ALL PASS (203 − 3 player tests − 3 minibar tests − 1 api test-block + 1 item rewrite ≈ 197; report exact). + +- [ ] **Step 6: Commit** + +```bash +git add -A && git commit -m "feat: navidrome deep link replaces in-app player" +``` + +--- + +### Task 3: Verification + redeploy + +- [ ] **Step 1:** `npm test && npm run typecheck && npm run build` — all green, both dists. +- [ ] **Step 2:** Restart the deployed instance (anchored pkill + setsid nohup pattern; verify in a separate invocation): health ok, SPA title served, `curl -X POST .../api/album/a1/played` → 404 (route gone). +- [ ] **Step 3:** No commit expected; report results. + +--- + +## Verification checklist (manual) + +- Item page for a ripped, matched album: 'Listen in Navidrome ↗' opens the correct album in Navidrome (desktop + phone) +- 'Last played X ago' still populates after a library sync (Navidrome-provided timestamps) +- No mini-bar anywhere; audio nowhere