docs(01): create gap closure plan 01-04 — device CRUD UI + CSS variable fix
This commit is contained in:
354
.planning/phases/01-foundation/01-04-PLAN.md
Normal file
354
.planning/phases/01-foundation/01-04-PLAN.md
Normal file
@@ -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"
|
||||
---
|
||||
|
||||
<objective>
|
||||
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.
|
||||
</objective>
|
||||
|
||||
<execution_context>
|
||||
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
||||
@$HOME/.claude/get-shit-done/templates/summary.md
|
||||
</execution_context>
|
||||
|
||||
<context>
|
||||
@.planning/PROJECT.md
|
||||
@.planning/ROADMAP.md
|
||||
@.planning/STATE.md
|
||||
@.planning/phases/01-foundation/01-CONTEXT.md
|
||||
@.planning/phases/01-foundation/01-VERIFICATION.md
|
||||
</context>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Add device CRUD UI — form in index.html, POST/DELETE/remove in app.js</name>
|
||||
<files>
|
||||
lightsync/frontend/index.html
|
||||
lightsync/frontend/app.js
|
||||
</files>
|
||||
<read_first>
|
||||
lightsync/frontend/index.html
|
||||
lightsync/frontend/app.js
|
||||
lightsync/api/devices.py
|
||||
</read_first>
|
||||
<action>
|
||||
**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
|
||||
<div class="panel" id="devices-panel" style="flex: 1; min-height: 0;">
|
||||
<div class="panel-header">DEVICES</div>
|
||||
<div class="panel-content" id="device-list">
|
||||
<span class="text-dim">loading...</span>
|
||||
</div>
|
||||
<form id="add-device-form" class="device-form">
|
||||
<input type="text" name="name" placeholder="Device name" required>
|
||||
<select name="strip_type" required>
|
||||
<option value="sk6812">SK6812</option>
|
||||
<option value="ws2801">WS2801</option>
|
||||
<option value="generic">Generic</option>
|
||||
</select>
|
||||
<input type="number" name="led_count" placeholder="LED count" min="1" max="1000" required>
|
||||
<input type="text" name="ip" placeholder="IP address" required>
|
||||
<input type="number" name="port" placeholder="Port" min="1" max="65535" value="21324" required>
|
||||
<button type="submit">+ ADD DEVICE</button>
|
||||
</form>
|
||||
</div>
|
||||
```
|
||||
|
||||
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 =>
|
||||
`<div class="device-row">
|
||||
<div class="device-row-info">
|
||||
<span class="text-accent">${escapeHtml(d.name)}</span>
|
||||
<span class="text-dim">${escapeHtml(d.strip_type)} / ${d.led_count} LEDs / ${escapeHtml(d.ip)}:${d.port}</span>
|
||||
</div>
|
||||
<button class="device-remove" onclick="removeDevice('${d.id}')" title="Remove device">×</button>
|
||||
</div>`
|
||||
).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;
|
||||
```
|
||||
</action>
|
||||
<verify>
|
||||
<automated>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</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- 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
|
||||
</acceptance_criteria>
|
||||
<done>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.</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Fix CSS variable naming to --accent and add panel IDs to index.html</name>
|
||||
<files>
|
||||
lightsync/frontend/style.css
|
||||
lightsync/frontend/index.html
|
||||
</files>
|
||||
<read_first>
|
||||
lightsync/frontend/style.css
|
||||
lightsync/frontend/index.html
|
||||
</read_first>
|
||||
<action>
|
||||
**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 `<div class="panel" style="flex: 0 0 120px;">`): Add `id="animations-panel"`:
|
||||
```html
|
||||
<div class="panel" id="animations-panel" style="flex: 0 0 120px;">
|
||||
```
|
||||
|
||||
2. The main-area timeline (currently line 35 `<main class="main-area">`): Wrap or add ID to the main element. Add a panel wrapper inside:
|
||||
```html
|
||||
<main class="main-area">
|
||||
<div class="panel" id="timeline-panel" style="flex: 1; display: flex; align-items: center; justify-content: center;">
|
||||
<span>[ TIMELINE — PHASE 2+ ]</span>
|
||||
</div>
|
||||
</main>
|
||||
```
|
||||
|
||||
3. The transport-bar footer (currently line 39 `<footer class="transport-bar">`): Add ID to the footer or add an inner panel:
|
||||
```html
|
||||
<footer class="transport-bar" id="transport-panel">
|
||||
<span>[ TRANSPORT — PHASE 2+ ]</span>
|
||||
</footer>
|
||||
```
|
||||
</action>
|
||||
<verify>
|
||||
<automated>cd /home/claude/led2 && grep -c "\-\-accent.*#00ffff" lightsync/frontend/style.css && grep "devices-panel" lightsync/frontend/index.html | head -1 && grep "animations-panel" lightsync/frontend/index.html | head -1 && grep "timeline-panel" lightsync/frontend/index.html | head -1 && grep "transport-panel" lightsync/frontend/index.html | head -1</automated>
|
||||
</verify>
|
||||
<acceptance_criteria>
|
||||
- lightsync/frontend/style.css contains `--accent: #00ffff` (not `--accent: var(...)`)
|
||||
- lightsync/frontend/style.css contains `--text-accent: var(--accent)` (alias, not a separate #00ffff value)
|
||||
- lightsync/frontend/style.css contains `--border-bright: var(--accent)` (alias, not a separate #00ffff value)
|
||||
- lightsync/frontend/style.css contains `.device-form` rule with `display: flex` and `flex-direction: column`
|
||||
- lightsync/frontend/style.css contains `.device-remove` rule with `cursor: pointer`
|
||||
- lightsync/frontend/index.html contains `id="devices-panel"`
|
||||
- lightsync/frontend/index.html contains `id="animations-panel"`
|
||||
- lightsync/frontend/index.html contains `id="timeline-panel"`
|
||||
- lightsync/frontend/index.html contains `id="transport-panel"`
|
||||
</acceptance_criteria>
|
||||
<done>CSS uses --accent: #00ffff as the canonical variable with --text-accent and --border-bright as aliases. All four panel IDs present in index.html. Device form and remove button have proper styling.</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<verification>
|
||||
1. `grep "add-device-form" lightsync/frontend/index.html` returns a match
|
||||
2. `grep "apiPost\|apiDelete\|removeDevice" lightsync/frontend/app.js` returns 3+ matches
|
||||
3. `grep "\-\-accent:.*#00ffff" lightsync/frontend/style.css` returns a match
|
||||
4. `grep "devices-panel\|animations-panel\|timeline-panel\|transport-panel" lightsync/frontend/index.html` returns 4 matches
|
||||
5. After deploying: open https://lightsync.groll.cloud, fill the add-device form, submit, see device appear in list, click remove, device disappears
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- The add-device form is visible in the DEVICES panel with all 5 inputs and submit button
|
||||
- Submitting the form creates a device via POST /api/devices and refreshes the list
|
||||
- Each device row shows a remove button that calls DELETE /api/devices/{id}
|
||||
- CSS variable --accent: #00ffff exists as the single source of truth for the cyan accent color
|
||||
- All four panel IDs (devices-panel, animations-panel, timeline-panel, transport-panel) are present in index.html
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.planning/phases/01-foundation/01-04-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user