---
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 =>
`
`
).join("");
```
Add the `removeDevice` function (after `loadDevices`):
```javascript
async function removeDevice(id) {
try {
await apiDelete(`/api/devices/${id}`);
await loadDevices();
} catch (err) {
console.error("[devices] remove failed:", err);
}
}
```
Add a form submit handler. After the `loadDevices()` call at the bottom of the file, add:
```javascript
document.getElementById("add-device-form").addEventListener("submit", async (e) => {
e.preventDefault();
const form = e.target;
const data = {
name: form.name.value.trim(),
strip_type: form.strip_type.value,
led_count: parseInt(form.led_count.value, 10),
ip: form.ip.value.trim(),
port: parseInt(form.port.value, 10),
};
try {
await apiPost("/api/devices", data);
form.reset();
form.port.value = "21324";
await loadDevices();
} catch (err) {
console.error("[devices] add failed:", err);
}
});
```
Note: Since app.js uses `type="module"`, the `removeDevice` function must be exported to global scope. Add this line after the function definition:
```javascript
window.removeDevice = removeDevice;
```
cd /home/claude/led2 && grep -c "add-device-form" lightsync/frontend/index.html && grep -c "apiPost" lightsync/frontend/app.js && grep -c "apiDelete" lightsync/frontend/app.js && grep -c "removeDevice" lightsync/frontend/app.js && grep -c "device-remove" lightsync/frontend/index.html
- lightsync/frontend/index.html contains `id="add-device-form"` with a form element
- lightsync/frontend/index.html contains `name="name"` input, `name="strip_type"` select with options sk6812/ws2801/generic, `name="led_count"` input, `name="ip"` input, `name="port"` input
- lightsync/frontend/index.html contains `type="submit"` button with text "+ ADD DEVICE"
- lightsync/frontend/index.html contains `class="device-remove"` button with onclick calling removeDevice
- lightsync/frontend/app.js contains `async function apiPost(path, data)` with `method: "POST"` and `Content-Type: application/json`
- lightsync/frontend/app.js contains `async function apiDelete(path)` with `method: "DELETE"`
- lightsync/frontend/app.js contains `async function removeDevice(id)` that calls `apiDelete` with `/api/devices/${id}`
- lightsync/frontend/app.js contains `addEventListener("submit"` on `add-device-form` that calls `apiPost("/api/devices", data)`
- lightsync/frontend/app.js contains `window.removeDevice = removeDevice` to expose function globally for onclick handler
The DEVICES panel has a working add-device form (name, strip_type select, led_count, ip, port, submit button). Each device row has a remove button. Form submit calls POST /api/devices, remove calls DELETE /api/devices/{id}, both refresh the device list.Task 2: Fix CSS variable naming to --accent and add panel IDs to index.html
lightsync/frontend/style.css
lightsync/frontend/index.html
lightsync/frontend/style.css
lightsync/frontend/index.html
**style.css changes:**
In the `:root` block (line 1-14), replace the current variable declarations with these renamed variables. The key change is adding `--accent: #00ffff` as the canonical accent variable. Keep `--text-accent` and `--border-bright` as aliases that reference `--accent`:
Replace lines 1-14 with:
```css
:root {
--bg-primary: #0a0a0a;
--bg-panel: #0f0f0f;
--bg-panel-dark: #080808;
--border-dim: #1a4a4a;
--accent: #00ffff;
--border-bright: var(--accent);
--text-primary: #cccccc;
--text-dim: #555555;
--text-accent: var(--accent);
--text-warning: #ff6600;
--font-mono: 'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
--font-size: 13px;
--panel-gap: 1px;
}
```
This makes `--accent` the single source of truth for `#00ffff`. The existing `--text-accent` and `--border-bright` usages throughout the CSS file continue to work because they now reference `--accent`.
Add CSS rules for the device form and remove button (append after the existing `button:hover` rule at the end of the file):
```css
/* Device form */
.device-form {
display: flex;
flex-direction: column;
gap: 4px;
padding: 8px 12px;
border-top: 1px solid var(--border-dim);
}
.device-form button[type="submit"] {
margin-top: 4px;
text-transform: uppercase;
letter-spacing: 0.08em;
}
/* Device remove button */
.device-remove {
background: none;
border: none;
color: var(--text-dim);
font-family: var(--font-mono);
font-size: 16px;
cursor: pointer;
padding: 0 4px;
line-height: 1;
flex-shrink: 0;
}
.device-remove:hover {
color: #ff3333;
border: none;
}
.device-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 6px 0;
border-bottom: 1px solid var(--bg-panel-dark);
gap: 8px;
}
.device-row-info {
display: flex;
flex-direction: column;
gap: 2px;
min-width: 0;
}
```
Note: The existing `.device-row` rule (lines 143-152) should be replaced by this updated version that adds `align-items: center` and `justify-content: space-between` to accommodate the remove button alongside the info.
**index.html changes:**
Add panel IDs to the remaining panels that are missing them. After Task 1, devices-panel already has its ID. The following still need IDs:
1. The ANIMATIONS panel (currently line 29 `
`): Add `id="animations-panel"`:
```html
```
2. The main-area timeline (currently line 35 ``): Wrap or add ID to the main element. Add a panel wrapper inside:
```html
[ TIMELINE — PHASE 2+ ]
```
3. The transport-bar footer (currently line 39 `