add SPA config UI, wire media/rss adapters, event-driven layout push
- 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)
This commit is contained in:
147
docs/k-frame-spa-handoff.md
Normal file
147
docs/k-frame-spa-handoff.md
Normal file
@@ -0,0 +1,147 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user