# `design.md` — building the visible app with `r`

Everything is authored **DB-direct via the CLI** (`r`, also `runner`/`acsetra`). There
are no spec/template files, no owned components, no themes. The renderer is a thin
engine: it reads value-set rows, expands CLI-authored component trees into bare DOM, and
paints them purely with CSS rows. A scope shows an app **only** when it has authored
routes/components; otherwise the root returns a bare `text/plain` response:
`Acsetra's Property`.

> **Scope is always `-S <scope>`** (a bare leaf like `crm` expands under your workspace
> root). It is never a positional argument. The op's positionals are its real args
> (a component code, a class code, a target). When unsure of a command's exact shape,
> run `r <group> <subverb> --help` or read `commands.md`.

## The typed registries (all value-set rows, all CLI-written)

| layer | holds | CLI |
|---|---|---|
| **components** | one row per component: a renderer-grammar `tree` + declared `parts` + `props` + component behavior nodes | `r component …` |
| **routes** | mounts a component onto a path (optional stable `instance` id) | `r component set_route …` |
| **css** | `css_classes` / `css_rules` / `css_attachments` — the whole design system | `r css …` |
| **assets** | `assets` / `asset_attachments` — media filled into named slots | `r asset …` |
| **head** | `document_head` — one ordered set of title/meta/link/style/script entries | `r head …` |
| **behavior** | `behaviors` / `behavior_attachments` — browser abilities (`mount(ctx)` snippets) mounted on a surface | `r behavior …` |

Beside them sits the **generic plane**: `r set …` mints value sets with *any other*
code — your own registries, rendered by your own components/behaviors. See *Generic
sets* below.

`r component init -S <scope>` / `r css init -S <scope>` / `r asset init -S <scope>` /
`r head init -S <scope>` bootstrap a plane in a scope (`r app create <name>` does the
whole app scope in one shot).

## AGENTS: file-upload UX is a composed pattern

Do **not** invent routes or probe for hidden upload components. Component trees
cannot carry browser file bytes directly; they render the controls and gallery.
The clean home for the browser orchestration is a **`behavior`** (see *Behavior —
operate the surface*): a `mount(ctx)` snippet attached to the upload control reads
the file, RUNs the app's upload-start pipeline, PUTs the bytes, then RUNs the
finalize/process pipeline via `ctx.run` — target-bound, lifecycle-clean, and with
run polling built in.

1. Read the `<input type="file">`, hash it, then RUN a storage `start_upload`.
2. Browser `PUT`s the file to the returned `upload_url`.
3. RUN the processing/finalize pipeline with the returned `logical_key`.
4. Render the result with a `repeat` over the relevant value set, usually
   `me_*` for per-user state.

For transformed media, the server-side transform belongs in a `snippet` worker
(`ffmpeg`, image tools, etc.), while the visible page is just upload control,
status text, and a gallery component. See `backend.md` before probing storage,
worker, or `me_*` behavior.

## Components — structure + component behavior nodes

A component is a row in the `components` set: `code` is the key, `meta` is the
definition. The `tree` is the safe renderer grammar (any safe HTML `tag`, `attrs`,
`classes`, `children`, `text`) plus three component-only keys:

- `part: "<name>"` — stamps `data-part` so CSS/assets can target that sub-element.
- `component: "<code>"` + `props: {…}` — references another component; `$prop`
  tokens in `text`/`attrs` are substituted from the props handed down.
- `kind:` — a **component behavior node**, the only non-styling primitive inside
  a component tree:
  - `submit` — a button that RUNs `pipeline` with the current form state.
  - `field` — an input bound to `name` (writes to state; `fieldType`, `placeholder`, `value`).
  - `repeat` — renders `component` once per row of `valueSet`, live (re-renders on change).

**Triggers & bindings — author WHEN things fire, in the row.** Any node can carry:
- `run: "<pipeline>"` + `on: click|input|change|blur|submit|mount|interval` (+ `debounce`/`every` ms)
  — RUN a pipeline on that event with the live form state. (`pipeline` is the
  shorthand for `run` on `click`; `submit` is just `run`-on-click in button form.)
  `on:"input"` + `debounce` is autosave-as-you-type; `mount`/`interval` need no gesture.
  The renderer drives the run's lifecycle as runtime CSS state on the trigger:
  `is-loading` while the launched run is pending, then `is-success`/`is-error` on
  a terminal status (auto-cleared after a beat) — so a `loading` CSS variant works
  for pipeline waits with **no behavior** (see CSS runtime states below). `interval`
  ticks opt out (they would flicker). Add `statusTarget: "<css-selector>"` to land
  that state on a nearby surface instead.
- `source: "<set>"` on a component reference — read that set at mount and hand its
  latest row down as the component's props (empty set ⇒ empty props, so a fresh
  user still gets a rendered form — no seeding). The renderer then LIVE-PATCHES
  bound inputs in place on the set's feed events, skipping the focused field — so
  live data and typing coexist without a rebuild. `live:false` makes it read-once.
- `bind: "<name>"` on a raw input — same state binding a `field` gets, for hand-rolled inputs.

Trees are validated at write time against the runtime grammar (safe tags only, no
event handlers, no `javascript:` URLs, **no inline `style`**), so the renderer
trusts stored rows. Cycles/depth are guarded during expansion.

```bash
r component init -S demo                                  # scope + components/routes sets
r component set contact-form -S demo --json '{"part":"root","tag":"form","children":[
    {"kind":"field","part":"name","name":"name","placeholder":"Name"},
    {"kind":"field","part":"email","name":"email","fieldType":"email"},
    {"kind":"submit","part":"save","pipeline":"save_contact","label":"Save"}]}'
r component set_route home contact-form --instance home-form -S demo     # mount it
```

`--json` carries the tree for `component set` (or `-` via `-b -` from stdin for large
trees). `r component describe <code> -S demo` shows child refs + mounting routes;
`r component validate -S demo` re-checks every tree + ref.

There are **no built-in `form`/`table`/`feed` components** — compose them from the
primitives: a table is a `<table>` with a `repeat` of a row component; a live list
is a `repeat` over a value set; a form is `field`s + a `submit`.

> **TRAP — never put an editable field inside a `repeat` bound to the set it
> saves to.** A `repeat` does `replaceChildren` on EVERY feed event: the focused
> input is destroyed mid-keystroke and typing is eaten. The rule is *editors
> patch, scenes repaint*: use `source` for anything the user types into (it
> live-patches bound inputs in place, skipping the focused field), and reserve
> `repeat` for read-only live lists and scenes that should visibly rebuild.

> **TRAP — a write nothing renders is a silent no-op on screen.** A pipeline that
> writes to a set no component binds (via `repeat`/`source`/a behavior `onSet`)
> commits the row and fires the feed, but no surface repaints — it looks like
> "nothing happened" even though the data landed. When a run seems to do nothing,
> check the binding first (`r compile <scope>` flags orphan writes), not the
> pipeline.

## CSS — the only styling path

Style lives in rows, never inline. A class is identity; rules are its declarations
(per variant, as a prop→value map); attachments bind a class to a surface.

```bash
r css set_class card -S demo
r css set_rule card base -S demo --json '{"display":"grid","gap":"12px","padding":"24px","border-radius":"12px"}'
r css attach card component:contact-form -S demo
```

Attachment targets (the second positional of `css attach`):

| target | matches |
|---|---|
| `component:<code>` | every instance of a component definition (`data-comp`) |
| `instance:<id>` | one placed instance (`data-comp-id`) |
| `part:<code>:<part>` | a declared part across instances (`data-part`) |
| `raw:<id>` | a node carrying `id` |
| `page:<code>` | a page surface |

Variants (the second positional of `css set_rule`): `base`, breakpoints
`mobile|tablet|desktop`, pseudos `hover|focus|active`, runtime states
`disabled|loading|error|success|dirty|empty` (the renderer toggles `is-<state>`).
`loading|success|error` are driven automatically for the lifetime of a run-trigger's
pipeline (see Triggers above) — author a `loading` variant on a `submit`'s class and
it lights up for the whole pending wait, no behavior needed. For animations, pass the
decls as a raw stylesheet: `--json '{"__raw__":"@keyframes spin{from{…}to{…}}"}'`,
emitted verbatim. **In a raw stylesheet you write the selectors, so target the
rendered namespace: a component `classes:["card"]` renders as `class="rf-card"`, so
write `.rf-card`, `[data-comp="…"]`, or `[data-part="…"]` — never bare `.card`, which
matches nothing and renders the page unstyled. `r compile`/`r doctor` now WARN when a
raw selector targets a bare class code that is actually an authored component class.**
`r css resolve <page> -S demo` shows the cascade; `r css validate -S demo` re-checks all.

## Assets — media into slots

`r asset add <code> <kind> -S <scope> …` registers an image/svg/icon/video/iframe
(validated at write time); `r asset attach <asset> <slot> -S <scope> …` fills a named
slot (`background`, `icon`, `thumbnail`, …) on a target (`--component`, `--part`,
`--raw`, `--page`). `resolve` and `validate` mirror the css verbs.

**Write-time `validate` is shape-only — it does NOT mean the image loads.** A
well-formed `https://…` URL that 404s passes every check. To confirm a remote URL
is actually live, probe it: `r asset add … --probe` HEADs the URL and *rejects*
the add if it is unreachable, 4xx/5xx, or returns an HTML error page instead of
media; `r asset check -S <scope> [--code <code>]` audits already-registered assets
the same way and reports `{code, status, content_type, ok}` per row. Storage-backed,
`data:`, inline-svg, icon, and youtube/vimeo embeds are skipped (nothing to fetch).
Always `--probe` (or `asset check`) any external image URL before trusting it.
(Storage-backed assets whose bytes the app owns are written by the `storage` worker
tier — see `backend.md`.)

## Head — document metadata

The `<head>` is a value set, not the renderer's HTML shell. `r head init -S <scope>`
creates `document_head` and seeds the document defaults (title, viewport, favicon);
each entry is one row — `{tag, attrs, text, enabled}` — ordered by `ordinal`. `--json`
fills the entry's **attrs**; `--tag`/`--text` are flags.

```text
r head set title            --text "My App" -S demo
r head set meta-description --tag meta --json '{"name":"description","content":"…"}' -S demo
r head set analytics        --tag script --json '{"src":"https://x.example/a.js","type":"module"}' -S demo
r head set analytics        --enabled false -S demo        # merges over the row to disable it
r head list -S demo                                        # head order
r head remove analytics -S demo
```

Tags are allowlisted (`title|meta|link|style|script|base`) and url attrs
(`href`/`src`) gated at write time; the renderer reconciles `<head>` in ordinal order
each load, so disabled/removed rows disappear. An authored `title` replaces the shell's
static one. Document-level privilege lives here, never in a component tree.

## Behavior — operate the surface

Components exist, CSS makes them appear, `source`/`repeat`/SSE breathe DB truth
into them, pipelines do durable effects. **Behavior** is the last plane: the user
can physically *operate* a surface — wheel zoom, drag, keyboard modes, RAF loops,
file reads, clipboard. It is the gesture/runtime layer between user input and the
DB commit.

A behavior is two value-set rows, authored DB-direct like css/assets:

- `behaviors` — one row per ability: a **snippet** whose body defines `mount(ctx)`
  (browser JS). There are no presets; every behavior is authored.
- `behavior_attachments` — one row per mount: a **target** (the same
  page/component/instance/part/raw grammar css/assets use) + a `config` bag.

```bash
r behavior init -S draw
r behavior add wheel-zoom -b - -S draw          # body from stdin (never escape JS into JSON)
r behavior attach wheel-zoom part:board:viewport -S draw \
    --config '{"min":0.2,"max":4,"commit":"save_viewport","source":"me_viewport"}'
r behavior list -S draw          # also: attachments | describe | validate
r behavior set_attachment_enabled wheel-zoom.part.board-viewport false -S draw   # disable one mount
r behavior set_attachment_enabled wheel-zoom.part.board-viewport true  -S draw   # re-enable it
```

The renderer reads both sets, mounts each attachment on every matching element,
and **tears it down on rerender** (full rebuild or `repeat` row replacement; a
late `source` subtree mounts its own behaviors when it appears) — so
listeners/RAF loops never leak and nothing double-mounts. `mount` receives one
tiny, stable `ctx`:

```
mount({ el, root, config,
        readSet, runPipeline,            // DB read + fire-and-forget RUN (raw fetch)
        run(pipeline,input),             // RUN + poll /runs/{id} to a terminal state -> the launch (output/error)
        onSet,                           // subscribe to a set's live feed (auto-unsubscribed)
        setVar(name,val), setData(k,v),  // behavior-owned CSS var / data-attr on el
        on(target,event,fn),             // listener with automatic cleanup
        raf(fn),                         // animation-frame loop with automatic cleanup
        cleanup(fn) })                   // register any teardown; mount may also RETURN one
```

A behavior definition toggles with `behavior set_enabled <code> <true|false>`; a
single placed mount toggles with `behavior set_attachment_enabled <attachment-code>
<true|false>`.

> **TRAP — listeners and loops added outside `ctx` leak.** The renderer can only
> tear down what it was handed. This leaks on every rerender (duplicate handlers,
> runaway RAF):
>
> ```js
> window.addEventListener("pointermove", track);   // never removed
> requestAnimationFrame(function loop(){ step(); requestAnimationFrame(loop); });
> ```
>
> This is the same ability, leak-free — cleanup is automatic on teardown:
>
> ```js
> ctx.on(window, "pointermove", track);
> ctx.raf(step);                        // one call; the renderer owns the loop
> // anything else: ctx.cleanup(() => observer.disconnect())
> ```

**Boundary.** A behavior may mutate *local* DOM, CSS vars, data-attrs, classes;
listen to events; keep temporary gesture state; read sets; and call pipelines. It
must **not** define durable truth (route that through a `config.commit` pipeline —
e.g. a `me_*` write — and let the feed paint the result), replace a component or a
CSS row, or become an unscoped global head script. `ctx.setVar` (an `el`-scoped
custom property) is the *only* sanctioned inline-style write; everything else
stays CSS-rows-only. The JS↔Python seam is exactly this: behavior = browser JS (the
gesture), `commit` = a pipeline (the durable Python effect).

## Generic sets — mint your own plane

Every typed registry above is the same storage shape: `value_option` rows in a
conventionally-named value set, read live, patched by feed events. **`r set` is the
untyped door to that shape.** It mints value sets with any non-reserved code, so you
can author a whole new plane — a **kit**: N generic sets that *describe* something plus
one host behavior that *renders* them. Generic rows work everywhere rows already work:
a component `repeat`/`source` binds to them, pipelines `writes_to` them, the feed
live-patches anything bound to them.

```bash
r set create world_objects --kind world -S myapp \
    --schema '{"type":"object","required":["kind"],"properties":{"kind":{"type":"string"}}}'
r set put    world_objects sun --json '{"kind":"sphere","glow":true}' -S myapp
r set put    world_objects sun --json '{"glow":false}' -S myapp        # MERGES one key (see trap)
r set list   -S myapp                  # every set at the scope, typed ones flagged
r set rows   world_objects -S myapp
r set declare_schema world_objects --json '{…}' -S myapp               # declare/replace contract
r set validate -S myapp                # grammar + size + schema conformance
r set drop   world_objects --force -S myapp
```

> **TRAP — `set put` MERGES by default.** A second `put` on the same row folds
> new keys over the old bag, so keys you meant to drop survive. Pass `--replace` to
> swap the whole bag. When a row looks haunted by a stale key, this is why.

**The reserved-code guard is the trust story.** The renderer consumes a handful
of set codes through code paths that *trust write-time validation* — raw CSS
passthrough, sanitized-svg `innerHTML`, head script/link rows, behavior JS bodies,
component trees. `set` **refuses** those codes (`components`, `routes`, `css_classes`,
`css_rules`, `css_attachments`, `assets`, `asset_attachments`, `document_head`,
`behaviors`, `behavior_attachments`) and the runtime-owned `me_*` prefix, pointing at
the typed verb that owns each. Everything else is open: generic rows are only ever
interpreted by the behaviors and pipelines the same author writes, so the kernel's
only opinions are mechanical bounds (row-code grammar, a 16k meta cap).

**Declared schemas.** A set may declare a row-meta contract — a small JSON-Schema
subset (`type`, `properties`, `required`, `enum`, `minimum`/`maximum`,
`minLength`/`maxLength`, `pattern`, `items`, `additionalProperties`, `description`).
`set put` REJECTS non-conforming rows, and `set validate` / `doctor` re-check every
stored row, so the contract holds against pipeline writes too. Kernel-stamped runtime
keys (any `_`-prefixed key) are exempt from `additionalProperties: false`.

**The kit pattern.** Sets carry the description, one host behavior carries the engine
(`r behavior add <kit>-host -b - -S <app>`, then `behavior attach` to a surface). The
host reads its sets with `ctx.readSet(...)`, renders them however it likes (DOM,
canvas, WebGL), and re-renders on feed events. Vocabulary lives in the host, never in
the kernel. Durable writes still route through pipelines — a snippet worker returning
`rows` into a kit's set redecorates every watching browser live.

**Remote sources (`set fetch_url`).** A kit's source text often already lives online.
`r set fetch_url <set> <row> <url> --key <metakey> -S <app>` imports it once: an
SSRF-guarded https GET (public addresses only, every redirect hop re-validated,
size-capped) lands the body in `meta.<key>` (default `source`) with `_src` provenance.
The runtime never loads remote code URLs — the row is the truth, so a dead/changed
origin can't reach a browser. Sources past the 16k meta cap belong as storage-backed
assets with the row as the pointer.

## Compile — the linked bundle (`r compile`)

The renderer serves the scope's whole renderable surface as **one linked,
content-hashed artifact**: every route's component tree **pre-expanded** server-side
(prop interpolation, behavior nodes, `source` markers, `data-comp`/`data-part` stamps),
the **compiled stylesheet text**, and the enabled head/asset/behavior rows. The shell
revalidates on every feed event: an unchanged hash answers `304` and the live DOM is
left alone — a write to a set nothing renders is a no-op instead of a teardown/rebuild.

`r compile <scope>` runs the same linker from the CLI — the gate the per-plane
validators can't be, because it sees **cross-plane** breaks: a route mounting a missing
component, a component cycle, an attachment whose class/asset/behavior vanished, a
`part:` target no reachable component declares, a trigger naming a pipeline that
doesn't exist, a `repeat`/`source` bound to a set nothing created. Errors exit
non-zero; the printed `hash` is the ETag browsers will see.

```bash
r compile crm            # link + diagnostics + content hash
```

## Doctor — the whole-subtree compiler

`r doctor <scope>` is the one command that proves a subtree *compiles*. Three passes,
errors gate (non-zero exit):

1. **Registries** — every plane validator (`css`, `asset`, `head`, `component`,
   `behavior`) plus generic sets. Proves DB shape, references, and cascade.
2. **Link** — `compile` (above) on every scope that authors a surface set.
3. **Programs** — the languages stored *inside* rows: behavior bodies parsed as JS,
   snippet-worker bodies compiled as Python, and raw `__raw__` CSS blobs
   brace-balance-checked.

Validators prove rows and parse, **not pixels** — a clean doctor run still doesn't
mean a selector matches or a layout looks right (the one common selector miss now
caught: a raw `__raw__` rule targeting a bare `.foo` that should be `.rf-foo` surfaces
as a Link-pass warning).

## The renderer floor

The renderer ships only a mechanical CSS floor — box model, `[hidden]`, form-control
font inheritance, and asset media display (`object-fit`). No colors, fonts, spacing,
chrome, or theme tokens: every pixel of identity is a CSS row.

> The local `./r` CLI also has a typed **shader** plane (GLSL fragment passes). It is
> **not** in the hosted CLI. For pixel-shading on hosted, author a WebGL `behavior`
> (the kit pattern) instead.
