nanobot-voice-interface/CARD_RUNTIME.md

127 lines
3.5 KiB
Markdown
Raw Normal View History

# Card Runtime
The app shell is responsible for layout, navigation, sessions, feed ordering, and workbench placement.
Cards are responsible for their own UI and behavior through a small dynamic runtime contract.
## Source Of Truth
- Live card templates live under `~/.nanobot/cards/templates`.
- Repo examples under `examples/cards/templates` are mirrors for development/reference.
- New cards must be added as `manifest.json + template.html + card.js`.
- `template.html` is markup and styles only. Do not put executable `<script>` tags in it.
## Module Contract
Each template must export:
```js
export function mount({ root, state, host }) {
// render and wire up DOM
return {
update?.({ root, item, state, host }) {},
destroy?.() {},
};
}
```
`root`
- The card root element already contains the rendered `template.html`.
`state`
- The current `template_state` object for this card/workbench item.
`host`
- The runtime host API described below.
## Lifecycle
- `mount()` runs once when the card is attached.
- `update()` runs when the card item or `template_state` changes.
- `destroy()` must clean up timers, listeners, observers, and in-flight UI handlers.
Cards must not rely on:
- `document.currentScript`
- global `window.__nanobot*` helpers
- inline script re-execution
## Host API
State
- `host.getState()`
- `host.replaceState(nextState)`
- `host.patchState(patch)`
Card context
- `host.setLiveContent(snapshot)`
- `host.getLiveContent()`
- `host.setSelection(selection)`
- `host.getSelection()`
- `host.clearSelection()`
Refresh
- `host.setRefreshHandler(handler)`
- `host.runRefresh()`
- `host.requestFeedRefresh()`
Tools
- `host.callTool(name, args?)`
- `host.startToolCall(name, args?)`
- `host.getToolJob(jobId)`
- `host.callToolAsync(name, args?, options?)`
- `host.listTools()`
Utilities
- `host.renderMarkdown(markdown, { inline? })`
- `host.copyText(text)`
- `host.getThemeName()`
- `host.getThemeValue("--theme-card-neutral-text")`
## Theme Tokens
Cards should inherit theme from shared CSS variables instead of hardcoding app-level colors.
Use these families first:
- `--theme-card-neutral-*` for standard data cards
- `--theme-card-warm-*` for timeline/planning cards
- `--theme-card-success-*` for inbox/positive cards
- `--card-*` for feed shell cards
- `--helper-card-*` for helper cards
- `--theme-text*`, `--theme-border*`, `--theme-accent*`, `--theme-status-*` for generic UI
Examples:
```html
<div style="background:var(--theme-card-neutral-bg); color:var(--theme-card-neutral-text); border:1px solid var(--theme-card-neutral-border)">
```
```js
statusEl.style.color = "var(--theme-status-live)";
const accent = host.getThemeValue("--theme-accent");
```
## Rules
- Persist all card-local durable state through `replaceState()` / `patchState()`.
- Use `setLiveContent()` for transient model-readable card context.
- Register at most one refresh handler with `setRefreshHandler()`.
- Always clear timers/intervals in `destroy()`.
- Prefer standard DOM APIs and small helper functions over framework-specific assumptions.
- Prefer shared theme tokens over hardcoded shell/surface colors.
## Validation
Run:
```bash
python3 scripts/check_card_runtime.py
node scripts/check_card_runtime_fixture.mjs
python3 scripts/sync_card_templates.py --check
```
The runtime check enforces:
- `manifest.json`, `template.html`, and `card.js` exist
- `template.html` has no inline script
- `card.js` parses cleanly
- no legacy runtime globals remain
- the lifecycle fixture contract can mount, update, and destroy cleanly