diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 3277174..09c546c 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -19,7 +19,7 @@ - [x] **DEV-01**: Register a device with: name, strip type (SK6812 / WS2801 / generic), LED count, target IP, target port - [x] **DEV-02**: Devices persist across sessions (stored in config file) -- [ ] **DEV-03**: Device list shown in UI — enable/disable per show +- [x] **DEV-03**: Device list shown in UI — enable/disable per show ### Animation Library @@ -60,7 +60,7 @@ ### UI / UX -- [ ] **UI-01**: Web app served by Python backend — no external hosting required +- [x] **UI-01**: Web app served by Python backend — no external hosting required - [ ] **UI-02**: Terminal/hacker aesthetic — dark theme, monospace fonts, CRT/scanline vibe; NOT generic SaaS look - [ ] **UI-03**: All primary operations accessible without mouse (keyboard shortcuts for play/pause/seek/undo) - [ ] **UI-04**: Live preview panel — simulated LED strip visualization in browser canvas, synced to playback diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md index 3121761..1abf332 100644 --- a/.planning/ROADMAP.md +++ b/.planning/ROADMAP.md @@ -12,7 +12,7 @@ LightSync is built bottom-up: data model first, audio engine second, UDP protoco Decimal phases appear between their surrounding integers in numeric order. -- [ ] **Phase 1: Foundation** - FastAPI skeleton, show file schema (Pydantic), device registry, terminal-aesthetic frontend shell +- [x] **Phase 1: Foundation** - FastAPI skeleton, show file schema (Pydantic), device registry, terminal-aesthetic frontend shell (completed 2026-04-05) - [ ] **Phase 2: Audio Engine** - MPV IPC bridge, waveform rendering, playback controls, Windows timer fix - [ ] **Phase 3: Communication Protocol** - UDP packet spec, DRGB/DNRGB/DRGBW encoders, animation library, software simulator - [ ] **Phase 4: Timeline Editor** - DAW UI (tracks, drag-place blocks, resize, delete), undo/redo, beat detection, snap-to-beat @@ -36,8 +36,8 @@ Decimal phases appear between their surrounding integers in numeric order. Plans: - [x] 01-01: Backend scaffold — FastAPI app, Pydantic show/device models, show store (JSON load/save), device registry -- [ ] 01-02: Frontend shell — HTML/CSS terminal aesthetic, WebSocket client stub, device panel, show panel -- [ ] 01-03: REST API — CRUD endpoints for devices and shows, static file serving, integration smoke test +- [x] 01-02: Frontend shell — HTML/CSS terminal aesthetic, WebSocket client stub, device panel, show panel +- [x] 01-03: REST API — CRUD endpoints for devices and shows, static file serving, integration smoke test **UI hint**: yes ### Phase 2: Audio Engine @@ -151,7 +151,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7 | Phase | Plans Complete | Status | Completed | |-------|----------------|--------|-----------| -| 1. Foundation | 1/3 | In Progress| | +| 1. Foundation | 3/3 | Complete | 2026-04-05 | | 2. Audio Engine | 0/3 | Not started | - | | 3. Communication Protocol | 0/3 | Not started | - | | 4. Timeline Editor | 0/4 | Not started | - | diff --git a/.planning/STATE.md b/.planning/STATE.md index 4c5a41e..6c8c3dd 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,14 +3,14 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: executing -stopped_at: Completed 01-foundation/01-01-PLAN.md -last_updated: "2026-04-05T18:49:18.330Z" +stopped_at: Completed 01-foundation/01-03-PLAN.md +last_updated: "2026-04-05T19:00:44.725Z" last_activity: 2026-04-05 progress: total_phases: 7 - completed_phases: 0 + completed_phases: 1 total_plans: 3 - completed_plans: 1 + completed_plans: 3 percent: 0 --- @@ -53,6 +53,7 @@ Progress: [░░░░░░░░░░] 0% *Updated after each plan completion* | Phase 01-foundation P01 | 4 | 2 tasks | 23 files | +| Phase 01-foundation P03 | 6 | 1 tasks | 9 files | ## Accumulated Context @@ -68,6 +69,8 @@ Recent decisions affecting current work: - [Phase 01-foundation]: Module-level state in main.py for registry/show_store — acceptable for Phase 1, Phase 2 migrates to Depends() - [Phase 01-foundation]: StaticFiles mounted last in create_app() — prevents catch-all interception of /api/* routes - [Phase 01-foundation]: encode_animation_cmd stubs return b'' — Phase 3 implements real UDP animation command encoding +- [Phase 01-foundation]: redirect_slashes=False on FastAPI — StaticFiles at root intercepted API redirects +- [Phase 01-foundation]: API collection routes use empty-string paths ('') not '/' — canonical /api/devices without redirect ### Pending Todos @@ -81,8 +84,8 @@ None yet. ## Session Continuity -Last session: 2026-04-05T18:49:18.325Z -Stopped at: Completed 01-foundation/01-01-PLAN.md +Last session: 2026-04-05T19:00:44.716Z +Stopped at: Completed 01-foundation/01-03-PLAN.md Resume file: None Next action: `/gsd:plan-phase 1` diff --git a/.planning/phases/01-foundation/01-03-SUMMARY.md b/.planning/phases/01-foundation/01-03-SUMMARY.md new file mode 100644 index 0000000..4ac60b3 --- /dev/null +++ b/.planning/phases/01-foundation/01-03-SUMMARY.md @@ -0,0 +1,160 @@ +--- +phase: 01-foundation +plan: "03" +subsystem: infra +tags: [fastapi, docker, traefik, authelia, alpine, uvicorn, routing] + +# Dependency graph +requires: + - phase: 01-01 + provides: FastAPI app, DeviceRegistry, ShowStore, models + - phase: 01-02 + provides: frontend shell, static files + +provides: + - Working device CRUD API (POST/GET/DELETE without trailing slash) + - Working show CRUD API (POST/GET list/GET by ID) + - Static file serving at / via FastAPI StaticFiles + - Device persistence across restarts via devices.json + - Show persistence via shows/ directory + - Dockerfile (python:3.11-alpine) + - docker-compose.prod.yml with Traefik + Authelia labels + - Deployed app at lightsync.groll.cloud + - LightSync entry added to start/projects.json + +affects: [02-audio, 03-timeline, all phases using API contracts] + +# Tech tracking +tech-stack: + added: [docker-compose v2.34.0 CLI plugin] + patterns: + - redirect_slashes=False on FastAPI app to prevent StaticFiles from intercepting API redirects + - API route paths use "" (empty string) not "/" to avoid trailing slash requirement + - hatchling wheel target requires explicit packages config for Docker builds + +key-files: + created: + - Dockerfile + - docker-compose.prod.yml + modified: + - lightsync/main.py + - lightsync/api/devices.py + - lightsync/api/shows.py + - lightsync/frontend/app.js + - pyproject.toml + - /home/claude/start/projects.json + +key-decisions: + - "redirect_slashes=False on FastAPI app — StaticFiles mount at / intercepted /api/* requests before FastAPI could redirect /api/devices -> /api/devices/" + - "API routes use empty string paths ('') not slash ('/') — makes /api/devices the canonical path without redirect" + - "docker-compose plugin installed manually as CLI plugin (v2.34.0) — system apt had no docker-compose-plugin" + +patterns-established: + - "Pattern: FastAPI + StaticFiles at root — always use redirect_slashes=False and empty-string route paths" + - "Pattern: Docker build — add [tool.hatch.build.targets.wheel] packages config for hatchling builds outside git" + +requirements-completed: [INF-01, DEV-01, DEV-02, DEV-03, SHW-01, SHW-02, UI-01] + +# Metrics +duration: 6min +completed: "2026-04-05" +--- + +# Phase 01 Plan 03: Integration and Deployment Summary + +**FastAPI device+show CRUD working end-to-end, deployed to lightsync.groll.cloud on python:3.11-alpine behind Authelia** + +## Performance + +- **Duration:** 6 min +- **Started:** 2026-04-05T18:53:06Z +- **Completed:** 2026-04-05T18:59:48Z +- **Tasks:** 1 +- **Files modified:** 7 (+ 2 created) + +## Accomplishments + +- Diagnosed and fixed StaticFiles/routing conflict — /api/devices (no trailing slash) was intercepted by StaticFiles before FastAPI redirect +- All API endpoints verified: POST/GET/DELETE devices, POST/GET list/GET by ID for shows, GET / returns LIGHTSYNC HTML +- Device persistence verified across server restart (devices.json round-trip) +- Show files confirmed saved as JSON with schema_version=1 in shows/ directory +- Docker image builds cleanly from python:3.11-alpine via hatchling wheel +- Container deployed and running at lightsync.groll.cloud behind Traefik + Authelia +- LightSync added to the start dashboard projects.json + +## Task Commits + +Each task was committed atomically: + +1. **Task 1: Integration test, routing fix, Docker deployment** - `a21962c` (feat) + +**Plan metadata:** _(docs commit follows)_ + +## Files Created/Modified + +- `Dockerfile` - python:3.11-alpine, pip install, COPY lightsync/, CMD python -m lightsync +- `docker-compose.prod.yml` - Traefik labels for lightsync.groll.cloud + authelia@docker middleware, edge network +- `lightsync/main.py` - Added redirect_slashes=False to FastAPI app +- `lightsync/api/devices.py` - Routes changed from "/" to "" (empty string) +- `lightsync/api/shows.py` - Routes changed from "/" to "" (empty string) +- `lightsync/frontend/app.js` - Changed /api/devices/ to /api/devices (no trailing slash) +- `pyproject.toml` - Added [tool.hatch.build.targets.wheel] packages = ["lightsync"] + +## Decisions Made + +- **redirect_slashes=False**: FastAPI's default redirect behavior (307 from /api/devices → /api/devices/) was being intercepted by the StaticFiles mount at root. Setting redirect_slashes=False and using empty-string route paths makes /api/devices the canonical URL with no redirect needed. +- **Empty-string route paths**: With redirect_slashes=False, routes must match exactly. Changed all collection routes from "/" to "" to match /api/devices and /api/shows directly. +- **docker-compose manual install**: System didn't have docker-compose-plugin in apt. Downloaded v2.34.0 binary from GitHub and installed as ~/.docker/cli-plugins/docker-compose. + +## Deviations from Plan + +### Auto-fixed Issues + +**1. [Rule 1 - Bug] Fixed StaticFiles intercepting API routes without trailing slash** +- **Found during:** Task 1 (smoke testing device CRUD) +- **Issue:** POST /api/devices returned 405, GET /api/devices returned 404. StaticFiles mount at "/" catches all paths including /api/devices (without trailing slash) before FastAPI's redirect to /api/devices/ can fire. +- **Fix:** Set redirect_slashes=False on FastAPI app, changed all collection routes from "/" to "" (empty string), updated frontend fetch URL to match +- **Files modified:** lightsync/main.py, lightsync/api/devices.py, lightsync/api/shows.py, lightsync/frontend/app.js +- **Verification:** POST /api/devices returns 201, GET /api/devices returns 200, all CRUD operations verified +- **Committed in:** a21962c + +**2. [Rule 1 - Bug] Fixed hatchling build failure in Docker** +- **Found during:** Task 1 (Docker build step) +- **Issue:** `pip install .` inside Docker fails with "Unable to determine which files to ship inside the wheel" — hatchling couldn't auto-detect package without git history +- **Fix:** Added `[tool.hatch.build.targets.wheel] packages = ["lightsync"]` to pyproject.toml +- **Files modified:** pyproject.toml +- **Verification:** Docker build succeeds, image tagged and running +- **Committed in:** a21962c + +**3. [Rule 3 - Blocking] Installed missing docker-compose plugin** +- **Found during:** Task 1 (deploy step) +- **Issue:** deploy.sh calls `docker compose -f` which requires the compose plugin; system had only docker.io 28.2.2 without compose plugin +- **Fix:** Downloaded docker-compose v2.34.0 binary from GitHub releases, installed to ~/.docker/cli-plugins/docker-compose +- **Files modified:** none (CLI plugin, not tracked) +- **Verification:** `docker compose version` reports v2.34.0, deploy.sh runs successfully +- **Committed in:** a21962c (infrastructure fix, no tracked file changes) + +--- + +**Total deviations:** 3 auto-fixed (2 Rule 1 bugs, 1 Rule 3 blocking) +**Impact on plan:** All three fixes necessary for the plan's core goal (working API + Docker deployment). No scope creep. + +## Issues Encountered + +- httpx `AsyncClient(app=app)` syntax deprecated — newer httpx requires `ASGITransport`. Switched to ASGITransport for smoke test but ultimately tested via live server + curl, which is more realistic anyway. +- Docker legacy builder warnings (buildx not installed) — non-blocking, build succeeds. + +## Next Phase Readiness + +- Full foundation is complete: device CRUD, show CRUD, StaticFiles serving, Docker deployment +- All Phase 1 success criteria met: device persistence, show JSON files, frontend loads, app in Docker at lightsync.groll.cloud +- Phase 2 can start building audio loading and timeline editor on this foundation +- API contracts are stable: /api/devices and /api/shows both work without trailing slashes + +## Self-Check: PASSED + +All files verified present. Task commit a21962c confirmed in git log. Container running (lightsync Up ~2 minutes). SUMMARY.md created. STATE.md updated. ROADMAP.md updated (Phase 1: 3/3 plans, Complete). Requirements DEV-03 and UI-01 marked complete. + +--- +*Phase: 01-foundation* +*Completed: 2026-04-05*