new rendering engine
This commit is contained in:
27
docs/adr/0004-domain-owned-rendering.md
Normal file
27
docs/adr/0004-domain-owned-rendering.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# 0004 — Domain-owned rendering, thin DisplayPort
|
||||
|
||||
**Status:** accepted
|
||||
**Date:** 2026-06-19
|
||||
|
||||
## Context
|
||||
|
||||
Adding text alignment, overflow scrolling, color markup, and theming to the client. These features require text measurement, line wrapping, scroll state management, and markup parsing. The question is whether this logic lives in the domain (client-domain crate) or in each display adapter (ESP32, desktop, terminal).
|
||||
|
||||
## Decision
|
||||
|
||||
All rendering intelligence lives in client-domain. The DisplayPort trait becomes a thin pixel-pusher with three methods: `draw_text_span`, `fill_rect`, `flush`. The domain's render engine parses markup, wraps text, computes alignment, manages scroll offsets, and emits positioned spans. Adapters only convert `Color(u8,u8,u8)` to native format and draw.
|
||||
|
||||
Font metrics are injected into the domain at init as a `FontMetrics` struct (char width/height per FontSize), enabling pure-math text measurement with no hardware callbacks.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Enriched DisplayPort** — `draw_rich_text(spans, bounds, alignment)` with each adapter handling wrapping, alignment, and markup internally. Duplicates complex logic across every adapter. Harder to test — requires hardware or mocks.
|
||||
|
||||
**Split ownership** — domain handles markup/scroll, adapter handles wrapping/alignment. Splits a single concern across layers, making behavior inconsistent across clients.
|
||||
|
||||
## Consequences
|
||||
|
||||
- All text behavior is testable without hardware — pure functions on structs.
|
||||
- New display adapters are trivial: implement three methods, provide font metrics.
|
||||
- Domain is coupled to monospace font assumption (width = char_count × char_width). Proportional fonts would require a measurement callback. Acceptable — all current targets use bitmap monospace fonts.
|
||||
- DisplayPort is a breaking change — existing adapters must be rewritten.
|
||||
@@ -1,147 +0,0 @@
|
||||
# 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