126 lines
3.5 KiB
Markdown
126 lines
3.5 KiB
Markdown
# 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
|