- 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)
148 lines
5.2 KiB
Markdown
148 lines
5.2 KiB
Markdown
# 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):
|
|
```json
|
|
{
|
|
"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):
|
|
```json
|
|
{
|
|
"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**:
|
|
```json
|
|
{
|
|
"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**:
|
|
```json
|
|
{"id": 1, "name": "dashboard", "layout": { "root": { ... } }}
|
|
```
|
|
|
|
## Pages to build
|
|
|
|
1. **Dashboard** — overview of connected clients, active data sources, current layout. Landing page.
|
|
2. **Data Sources** — CRUD list. Form: name, source_type (select), URL, API key, poll interval, headers.
|
|
3. **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.
|
|
4. **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.
|
|
5. **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:
|
|
1. **Dev**: SPA runs on Vite dev server (:5173), proxies API calls to :3000. Add proxy config to `vite.config.ts`.
|
|
2. **Prod**: `bun run build` outputs to `spa/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 template
|
|
- `shadcn` — for component usage, composition, and styling patterns
|