--- phase: 01-foundation plan: 01 subsystem: backend tags: [python, fastapi, pydantic, uvicorn, websocket, nginx, terminal-ui] # Dependency graph requires: [] provides: - "Python package lightsync/ with all backend modules" - "Pydantic v2 DeviceConfig and ShowModel with full schema" - "BaseDevice ABC + SK6812/WS2801 concrete implementations" - "DeviceRegistry with JSON persistence (devices.json)" - "ShowStore with JSON save/load (shows/ directory)" - "FastAPI app with lifespan, REST stubs, WebSocket stub" - "Terminal-aesthetic frontend shell (DAW skeleton)" - "python -m lightsync entry point serving on port 8000" affects: [phase-01-02, phase-02, phase-03, phase-04, phase-05, phase-06, phase-07] # Tech tracking tech-stack: added: - "FastAPI 0.115+ — web framework, REST, WebSocket, StaticFiles" - "uvicorn[standard] 0.34+ — ASGI server with WebSocket support" - "Pydantic v2 — model validation and JSON serialization" - "aiofiles 24+ — async file I/O for show JSON and device registry" - "python-dotenv — HOST/PORT config from .env file" - "structlog — structured logging" - "hatchling — build backend for pyproject.toml" patterns: - "lifespan context manager for startup/shutdown (replaces deprecated @app.on_event)" - "Module-level registry/show_store refs in main.py — accessed via `import lightsync.main as state`" - "StaticFiles LAST — API routes registered before mount to prevent catch-all interception" - "Pydantic v2 APIs: model_dump_json(), model_validate_json(), model_validate() — never v1 .json()/.parse_obj()" - "BaseDevice ABC + _DEVICE_CLASSES dict — adding new strip type = new file + one dict entry (INF-02)" - "pathlib.Path throughout — cross-platform safe file paths" - "aiofiles for all file I/O in async contexts — non-blocking" key-files: created: - pyproject.toml - .env - .gitignore - lightsync/__init__.py - lightsync/__main__.py - lightsync/main.py - lightsync/models/__init__.py - lightsync/models/device.py - lightsync/models/show.py - lightsync/devices/__init__.py - lightsync/devices/base.py - lightsync/devices/sk6812.py - lightsync/devices/ws2801.py - lightsync/devices/registry.py - lightsync/store/__init__.py - lightsync/store/show_store.py - lightsync/api/__init__.py - lightsync/api/devices.py - lightsync/api/shows.py - lightsync/api/ws.py - lightsync/frontend/index.html - lightsync/frontend/style.css - lightsync/frontend/app.js modified: [] key-decisions: - "aiofiles for all JSON file I/O — keeps async handlers non-blocking per research guidance" - "Module-level state in main.py for registry/show_store — acceptable for Phase 1, Phase 2 migrates to Depends()" - "StaticFiles mounted last — required to prevent catch-all interception of /api/* routes" - "encode_animation_cmd stubs return b'' — Phase 3 implements real UDP animation command encoding" - ".gitignore excludes devices.json and shows/ — these are runtime data, not source" patterns-established: - "lifespan context manager for app startup/shutdown" - "BaseDevice ABC + _DEVICE_CLASSES registry dict for extensible device type system" - "Pydantic v2 serialization patterns (model_dump_json, model_validate_json)" - "aiofiles for async file I/O in FastAPI route handlers" - "CSS custom properties for terminal aesthetic — all colors via :root vars" requirements-completed: - INF-01 - INF-02 - DEV-01 - DEV-02 - SHW-01 - SHW-02 # Metrics duration: 4min completed: 2026-04-05 --- # Phase 1 Plan 01: Backend Scaffold Summary **FastAPI backend scaffold with Pydantic v2 models, device ABC, JSON persistence stores, REST+WebSocket stubs, and terminal-aesthetic DAW skeleton frontend — all runnable via `python -m lightsync`** ## Performance - **Duration:** ~4 min - **Started:** 2026-04-05T18:43:46Z - **Completed:** 2026-04-05T18:48:03Z - **Tasks:** 2 (both auto) - **Files created:** 23 ## Accomplishments ### Task 1: Package structure, Pydantic models, device abstractions (commit: 061edfc) - `pyproject.toml` with all Phase 1 dependencies (FastAPI, uvicorn, Pydantic v2, aiofiles, structlog) - `lightsync/models/device.py` — `DeviceConfig` Pydantic model with `StripType` literal, UUID auto-ID, validation constraints - `lightsync/models/show.py` — `ShowModel` with `schema_version=1`, `AudioRef`, `TrackModel`, `CueModel`, `AnalysisBlock` - `lightsync/devices/base.py` — `BaseDevice` ABC with `encode_frame`, `encode_animation_cmd`, `bytes_per_pixel` - `lightsync/devices/sk6812.py` — SK6812 RGBW (4 bytes/pixel), encode_frame builds RGBW bytearray - `lightsync/devices/ws2801.py` — WS2801 RGB (3 bytes/pixel), encode_frame builds RGB bytearray ### Task 2: Registry, show store, FastAPI app, frontend shell (commit: 5e10272) - `lightsync/devices/registry.py` — `DeviceRegistry` with `_DEVICE_CLASSES` dict, load/save (aiofiles), add/remove/get/list_all/instantiate - `lightsync/store/show_store.py` — `ShowStore` with async save/load, `list_ids()` via glob - `lightsync/main.py` — `create_app()` with lifespan hook, API routes registered before `StaticFiles` mount - `lightsync/api/devices.py` — `GET /`, `POST /`, `DELETE /{id}` — full CRUD with save-on-write - `lightsync/api/shows.py` — `GET /`, `POST /`, `GET /{id}` — create and retrieve shows - `lightsync/api/ws.py` — `ConnectionManager` + `/ws` WebSocket endpoint with ack echo - `lightsync/frontend/index.html` — DAW skeleton: header, sidebar (DEVICES + ANIMATIONS), TIMELINE main area, TRANSPORT footer - `lightsync/frontend/style.css` — Terminal aesthetic: `#0a0a0a` bg, `#00ffff` cyan accent, `border-radius: 0` everywhere, CSS Grid layout - `lightsync/frontend/app.js` — `LightSyncClient` WS with reconnect, `loadDevices()` populating device list - `.gitignore` — excludes runtime data (`devices.json`, `shows/`), `.venv/`, `__pycache__/` ## Verification Results All verification checks passed: - `pip install -e ".[dev]"` — installs successfully - `python -c "from lightsync.models.show import ShowModel; s = ShowModel(name='test'); assert s.schema_version == 1"` — passes - `python -c "from lightsync.devices.registry import DeviceRegistry; print('OK')"` — passes - `python -m lightsync` — starts server on port 8000, serves `GET /api/devices/` → 200 `[]` ## Task Commits | Task | Commit | Description | |------|--------|-------------| | 1 | 061edfc | Package structure, Pydantic models, device abstractions | | 2 | 5e10272 | Registry, show store, FastAPI app, frontend shell | ## Decisions Made - Module-level `registry` and `show_store` refs in `main.py` — acceptable for Phase 1 single-process server; Phase 2 should migrate to `Depends()` for testability - `encode_animation_cmd` stubs in SK6812/WS2801 return `b""` — Phase 3 implements real UDP frame encoding - Google Fonts (JetBrains Mono) loaded from CDN — offline fallback to Consolas via CSS custom property chain ## Deviations from Plan None — plan executed exactly as written. ## Known Stubs These are intentional per-plan stubs, documented in the plan spec: | Stub | File | Line | Reason | |------|------|------|--------| | `encode_animation_cmd` returns `b""` | lightsync/devices/sk6812.py | 28 | Phase 3 implements real UDP animation command encoding | | `encode_animation_cmd` returns `b""` | lightsync/devices/ws2801.py | 26 | Phase 3 implements real UDP animation command encoding | | ANIMATIONS panel placeholder | lightsync/frontend/index.html | — | Phase 3+ per design decision D-08 | | TIMELINE panel placeholder | lightsync/frontend/index.html | — | Phase 2+ per design decision D-09 | | TRANSPORT bar placeholder | lightsync/frontend/index.html | — | Phase 2+ per design decision D-10 | All stubs are DAW skeleton placeholders (per D-06 decision: full panel layout from day 1) or device encoding stubs for UDP (Phase 3). They do not prevent the plan's goal from being achieved — the backend scaffold, persistence, and server entry point are fully functional. ## Next Phase Readiness - Ready for 01-02: frontend UI details and static file serving verification - Ready for 01-03: Docker deploy to groll.cloud - All future phases can import from `lightsync.models`, `lightsync.devices`, `lightsync.store` - `_DEVICE_CLASSES` dict ready to receive new strip types in Phase 5 --- *Phase: 01-foundation* *Completed: 2026-04-05*