diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md
index 1abf332..b82dd16 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.
-- [x] **Phase 1: Foundation** - FastAPI skeleton, show file schema (Pydantic), device registry, terminal-aesthetic frontend shell (completed 2026-04-05)
+- [ ] **Phase 1: Foundation** - FastAPI skeleton, show file schema (Pydantic), device registry, terminal-aesthetic frontend shell (gap closure in progress)
- [ ] **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
@@ -32,12 +32,13 @@ Decimal phases appear between their surrounding integers in numeric order.
3. The web app loads in a browser with terminal/hacker aesthetic — dark background, monospace fonts, no generic SaaS look
4. FastAPI serves the frontend and exposes REST stubs for shows and devices
5. Adding a new strip type requires only a new device class — no changes to core engine code
-**Plans**: 3 plans
+**Plans**: 4 plans
Plans:
- [x] 01-01: Backend scaffold — FastAPI app, Pydantic show/device models, show store (JSON load/save), device registry
- [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
+- [ ] 01-04: Gap closure — device CRUD UI (add form, remove button), CSS variable naming fix, panel IDs
**UI hint**: yes
### Phase 2: Audio Engine
@@ -151,7 +152,7 @@ Phases execute in numeric order: 1 → 2 → 3 → 4 → 5 → 6 → 7
| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
-| 1. Foundation | 3/3 | Complete | 2026-04-05 |
+| 1. Foundation | 3/4 | Gap closure | - |
| 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/phases/01-foundation/01-04-PLAN.md b/.planning/phases/01-foundation/01-04-PLAN.md
new file mode 100644
index 0000000..8e7ad2d
--- /dev/null
+++ b/.planning/phases/01-foundation/01-04-PLAN.md
@@ -0,0 +1,354 @@
+---
+phase: 01-foundation
+plan: 04
+type: execute
+wave: 3
+depends_on:
+ - 01-03
+files_modified:
+ - lightsync/frontend/index.html
+ - lightsync/frontend/style.css
+ - lightsync/frontend/app.js
+autonomous: true
+requirements:
+ - DEV-03
+ - UI-02
+gap_closure: true
+
+must_haves:
+ truths:
+ - "The DEVICES panel has an add-device form with inputs for name, strip_type, led_count, ip, port and a submit button"
+ - "Submitting the add-device form creates a device via POST /api/devices and the new device appears in the list"
+ - "Each device row has a remove button that calls DELETE /api/devices/{id} and refreshes the list"
+ - "CSS uses --accent: #00ffff as the canonical accent variable name"
+ - "index.html has panel IDs: devices-panel, animations-panel, timeline-panel, transport-panel"
+ artifacts:
+ - path: "lightsync/frontend/index.html"
+ provides: "Add-device form and panel IDs"
+ contains: "add-device-form"
+ - path: "lightsync/frontend/app.js"
+ provides: "Device CRUD functions: apiPost, apiDelete, removeDevice, form handler"
+ contains: "apiPost"
+ - path: "lightsync/frontend/style.css"
+ provides: "Canonical --accent CSS variable"
+ contains: "--accent: #00ffff"
+ key_links:
+ - from: "lightsync/frontend/app.js"
+ to: "/api/devices"
+ via: "apiPost for form submit, apiDelete for remove button"
+ pattern: "apiPost.*api/devices"
+ - from: "lightsync/frontend/index.html"
+ to: "lightsync/frontend/app.js"
+ via: "form#add-device-form submit event listener"
+ pattern: "add-device-form"
+---
+
+
+Close two verification gaps from 01-VERIFICATION.md: (1) add device CRUD UI — form, POST, DELETE, remove buttons; (2) fix CSS variable naming to use --accent and add missing panel IDs to index.html.
+
+Purpose: Completes DEV-03 (device list with UI interaction) and UI-02 (CSS contract alignment with plan spec).
+Output: Updated index.html, app.js, and style.css with full device management UI and corrected CSS variable naming.
+
+
+
+@$HOME/.claude/get-shit-done/workflows/execute-plan.md
+@$HOME/.claude/get-shit-done/templates/summary.md
+
+
+
+@.planning/PROJECT.md
+@.planning/ROADMAP.md
+@.planning/STATE.md
+@.planning/phases/01-foundation/01-CONTEXT.md
+@.planning/phases/01-foundation/01-VERIFICATION.md
+
+
+
+
+
+ Task 1: Add device CRUD UI — form in index.html, POST/DELETE/remove in app.js
+
+ lightsync/frontend/index.html
+ lightsync/frontend/app.js
+
+
+ lightsync/frontend/index.html
+ lightsync/frontend/app.js
+ lightsync/api/devices.py
+
+
+ **index.html changes:**
+
+ In the sidebar, the first panel (DEVICES panel, currently lines 23-28) must get an id and have the add-device form inserted after the device-list div. Replace the current DEVICES panel block:
+
+ ```html
+
+
DEVICES
+
+ loading...
+
+
+
+ ```
+
+ The form goes AFTER the panel-content div but still inside the panel div, so it sits at the bottom of the devices panel. The form class is "device-form" (styles added in Task 2).
+
+ **app.js changes:**
+
+ Add two API helper functions before the LightSyncClient class (near the top, after the WS_URL const):
+
+ ```javascript
+ async function apiPost(path, data) {
+ const res = await fetch(path, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(data),
+ });
+ if (!res.ok) throw new Error(`POST ${path}: HTTP ${res.status}`);
+ return res.json();
+ }
+
+ async function apiDelete(path) {
+ const res = await fetch(path, { method: "DELETE" });
+ if (!res.ok && res.status !== 204) throw new Error(`DELETE ${path}: HTTP ${res.status}`);
+ }
+ ```
+
+ Modify the `loadDevices()` function: change the device row rendering to include a remove button. Replace the current `list.innerHTML = devices.map(...)` block with:
+
+ ```javascript
+ list.innerHTML = devices.map(d =>
+ `