- React SPA: dashboard, data sources CRUD, widgets CRUD, layout builder, presets. TanStack Router + Query, shadcn/ui, Vite proxy to :3000 - wire media + rss adapters into polling loop, remove xtb source type - media adapter: read username/password from headers, proper subsonic auth - event handler: subscribe to LayoutChanged, push screen update to clients - fix clippy warnings across workspace (Default impls, collapsible ifs, redundant closures, is_none_or, unused imports)
5.2 KiB
K-Frame SPA Handoff
What is K-Frame
IoT dashboard system. Server aggregates data from configurable sources and pushes to ESP32 display clients via TCP. The server is fully functional — SQLite config, REST API, TCP broadcasting, data source polling. ESP32 firmware works end-to-end (display renders widgets). Now needs a config UI.
What this session should build
A React SPA (config/admin UI) for the K-Frame server. The SPA is at /mnt/drive/dev/k-frame/spa/ — fresh Vite + React 19 + shadcn/ui + TanStack Router + TanStack Query setup. Currently shows a placeholder page.
Existing artifacts to read first
- Design spec:
docs/superpowers/specs/2026-06-18-k-frame-design.md - Domain glossary:
CONTEXT.md - ADRs:
docs/adr/0001-event-driven-cqrs.md,0002-static-dispatch-over-trait-objects.md,0003-postcard-over-flatbuffers.md - API types (DTO definitions):
crates/api-types/src/— widget.rs, data_source.rs, layout.rs, preset.rs. These define the exact JSON shapes the API accepts/returns.
REST API (server runs on :3000)
All endpoints return/accept JSON.
| Method | Path | Description |
|---|---|---|
| GET | /api/widgets | List all widgets |
| POST | /api/widgets | Create widget |
| GET | /api/widgets/{id} | Get widget |
| PUT | /api/widgets/{id} | Update widget |
| DELETE | /api/widgets/{id} | Delete widget |
| GET | /api/data-sources | List all data sources |
| POST | /api/data-sources | Create data source |
| GET | /api/data-sources/{id} | Get data source |
| PUT | /api/data-sources/{id} | Update data source |
| DELETE | /api/data-sources/{id} | Delete data source |
| GET | /api/layout | Get current layout (nullable) |
| PUT | /api/layout | Update layout |
| GET | /api/presets | List presets |
| POST | /api/presets | Create preset |
| GET | /api/presets/{id} | Get preset |
| DELETE | /api/presets/{id} | Delete preset |
| POST | /api/presets/{id}/load | Load preset as active layout |
Key JSON shapes
Widget (create/update):
{
"id": 1,
"name": "weather",
"display_hint": "icon_value",
"data_source_id": 10,
"mappings": [
{"source_path": "$.main.temp", "target_key": "temperature"},
{"source_path": "$.weather[0].icon", "target_key": "icon"}
],
"max_data_size": 2048
}
display_hint values: "icon_value", "text_block", "key_value"
Data Source (create/update):
{
"id": 10,
"name": "weather",
"source_type": "weather",
"poll_interval_secs": 300,
"url": "https://api.openweathermap.org/...",
"api_key": "xxx",
"headers": []
}
source_type values: "weather", "media", "rss", "http_json", "webhook"
Layout:
{
"root": {
"type": "container",
"direction": "row",
"gap": 4,
"padding": 2,
"children": [
{
"sizing": {"type": "flex", "value": 1},
"node": {"type": "leaf", "widget_id": 1}
},
{
"sizing": {"type": "fixed", "value": 80},
"node": {"type": "leaf", "widget_id": 2}
}
]
}
}
Nodes are recursive — containers can nest.
Preset:
{"id": 1, "name": "dashboard", "layout": { "root": { ... } }}
Pages to build
- Dashboard — overview of connected clients, active data sources, current layout. Landing page.
- Data Sources — CRUD list. Form: name, source_type (select), URL, API key, poll interval, headers.
- Widgets — CRUD list. Form: name, display_hint (select), data_source_id (select from existing sources), key mappings (dynamic list of source_path + target_key pairs), max_data_size.
- Layout Builder — visual tree editor. Add containers (row/column), nest them, place widgets as leaves, set sizing (fixed/flex), gap, padding. This is the most complex page.
- Presets — save current layout as preset, load presets, delete presets.
SPA tech stack (already set up)
- React 19 + TypeScript
- Vite 8
- shadcn/ui (components already installed in
src/components/ui/) - TanStack Router (not yet configured with routes)
- TanStack Query (not yet configured with provider)
- Tailwind CSS 4
- Bun (lockfile is bun.lock)
Server integration
The SPA's built files need to be served by the Axum server. Two approaches:
- Dev: SPA runs on Vite dev server (:5173), proxies API calls to :3000. Add proxy config to
vite.config.ts. - Prod:
bun run buildoutputs tospa/dist/, Axum serves these as static files. Need to add static file serving to the http-api adapter.
The Vite proxy setup is needed first so development works.
User preferences
- Concise, no filler
- No mocking — test against real API
- Clean code, modules, no huge files
- shadcn components for all UI elements
- TanStack Router for routing, TanStack Query for data fetching
- No Co-Authored-By in commits
What NOT to change
- No changes to Rust crates (domain, application, adapters, etc.)
- No changes to the ESP32 firmware
- API is stable — build against it as-is
Suggested skills
superpowers:brainstorming— for designing the layout builder UX (most complex page)frontend-design— for visual design direction, making it not look like a default templateshadcn— for component usage, composition, and styling patterns