From bc3db9e4f7a5a24d2ec7d41904e7ee676a04ffd0 Mon Sep 17 00:00:00 2001 From: Samu Date: Thu, 3 Sep 2026 20:37:38 +0200 Subject: [PATCH] docs: wave 1 implementation plan (15 tasks) --- docs/superpowers/plans/2026-09-03-wave1.md | 2582 ++++++++++++++++++++ 1 file changed, 2582 insertions(+) create mode 100644 docs/superpowers/plans/2026-09-03-wave1.md diff --git a/docs/superpowers/plans/2026-09-03-wave1.md b/docs/superpowers/plans/2026-09-03-wave1.md new file mode 100644 index 0000000..55449a0 --- /dev/null +++ b/docs/superpowers/plans/2026-09-03-wave1.md @@ -0,0 +1,2582 @@ +# Wave 1 Implementation Plan (plan 3) + +> **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:** Ship Wave 1: schema versioning + live migration, built-in album player with last-played tracking, loan tracking, rip queue, stats wall, and JSON export + SQLite backups. + +**Architecture:** Extends the existing Fastify/SQLite backend (server/) and React SPA (web/) without changing the frozen plan-1 API. New routes: stream/album proxy, loans, stats, export/backup. First schema migration via `app_meta.schema_version`. Player is a global context above the router so audio survives navigation. + +**Tech Stack:** unchanged (Fastify 5, better-sqlite3, React 19, Vite, Vitest). Spec: `docs/superpowers/specs/2026-09-03-wave1-hygiene-listening-design.md`. + +**Prerequisites:** main (post plan 2), 147/147 tests. Branch: `wave1-hygiene-listening`. Node >= 20. + +**Stamping refinement vs spec:** Subsonic's recent-list entries don't reliably carry a played timestamp. `last_played_at` is therefore stamped from two sources: (1) sync, when an album entry reports one (`played`/`playedAt` field, OpenSubsonic-style — opportunistic, null-safe), and (2) the built-in player, via `POST /api/album/:subsonicId/played` on playback start. UX is identical. + +--- + +## File structure (plan 3) + +``` +server/src/ +├── db.ts # MODIFY: versioned migrate(), loans table, last_played_at +├── config.ts # MODIFY: backupsDir +├── subsonic.ts # MODIFY: getAlbum(), getRecentAlbums(), public url() +├── sync.ts # MODIFY: stamp last_played_at +├── ripstatus.ts # MODIFY: findMatchedAlbum() +├── routes/ +│ ├── collectionRoutes.ts # MODIFY: detail gains matchedAlbum, onLoan filter +│ ├── streamRoutes.ts # NEW: /api/stream/:songId, /api/album/:id/tracks, /api/album/:id/played +│ ├── loanRoutes.ts # NEW: loans CRUD +│ ├── statsRoutes.ts # NEW: /api/stats +│ └── dataRoutes.ts # NEW: /api/export, /api/backup, /api/backups +└── test/ (new: migrate.test.ts, loans.test.ts, stats.test.ts, data.test.ts, stream.test.ts; modified: subsonic.test.ts, sync.test.ts, collection.test.ts) +web/src/ +├── types.ts # MODIFY: Track, Stats, Loan, Backups, matchedAlbum +├── api.ts # MODIFY: new methods +├── player/PlayerContext.tsx # NEW: global player state +