# Tour: Your First Grade-A App — Settlement Desk

You'll build a **Settlement Desk**: a card that reads a settlement agreement's live on-chain
state (status, pooled principal, percent repaid) and shows its self-declared trust facets. Every
value is an on-chain read, so the whole app is **Grade A**. Along the way you'll hit — and fix —
the one import/compile pitfall every builder user meets once.

Time: ~10 minutes to a rendering preview; add the deploy/seed steps to see live data.

---

## 1. Import the app

Open the Visual Builder (`/builder/index.html`) → **⋯ More → Import JSON** → paste this file.
It's a bare node tree — exactly what Import accepts and what **Export JSON** produces.

```json
{
  "type": "box",
  "direction": "column",
  "children": [
    { "type": "text", "content": "Settlement Desk", "role": "heading" },
    { "type": "text", "content": "A fully on-chain settlement agreement, rendered straight from chain state." },
    { "type": "text", "content": "Live settlement state", "role": "heading" },
    {
      "type": "box",
      "direction": "row",
      "children": [
        { "type": "slot", "bind": "settle-status",    "argValues": { "agreement": "0x1111111111111111111111111111111111111111", "assetDecimals": "6", "fracDigits": "2" } },
        { "type": "slot", "bind": "settle-principal", "argValues": { "agreement": "0x1111111111111111111111111111111111111111", "assetDecimals": "6", "fracDigits": "2" } },
        { "type": "slot", "bind": "settle-repaid",    "argValues": { "agreement": "0x1111111111111111111111111111111111111111", "assetDecimals": "6", "fracDigits": "2" } }
      ]
    },
    { "type": "text", "content": "Trust, declared on-chain", "role": "heading" },
    { "type": "slot", "bind": "trust-facets", "argValues": { "subject": "0x1111111111111111111111111111111111111111" } }
  ]
}
```

The canvas renders the layout immediately. The `0x1111…1111` addresses are **placeholders** —
you'll replace them with your deployed agreement in step 5. Until then, the slots read nothing,
which is exactly what step 4 is about.

---

## 2. A heading is a `text` node with a role — not `type: "heading"`

**This is the gotcha.** It is tempting to write a heading as `{ "type": "heading", … }`, and the
canvas will even *draw* it — so it looks fine. But when you **compile** it with the SDK's `compile()` (or
**preview on-chain** in a builder from before its heading safety net), you get:

```
On-chain preview failed: u8 out of range: undefined.
```

That error is **not** about your RPC or interpreter, even though the builder suggests checking
them. It is thrown **client-side, during compile, before any `eth_call`**. The compiler turns
each node's `type` into a one-byte tag, and the set of real node types is:

> `text · box · button · input · icon · link · slot · image · ref`

`heading` is **not** one of them. The canvas renderer tolerates `type: "heading"` for display,
but the compiler doesn't recognize it, so the tag comes back `undefined` and the byte writer
throws `u8 out of range: undefined`. The renderer and the compiler disagreed, and the compiler
is the one that matters for anything that touches the chain.

**The fix** (already applied in the JSON above): a heading is a `text` node carrying the ARIA
heading role.

```json
{ "type": "heading", "content": "Settlement Desk" }              // ✗ compiles to u8 out of range
{ "type": "text", "content": "Settlement Desk", "role": "heading" }  // ✓ a real, compilable heading
```

Rule of thumb: **if a value isn't one of the nine node types, it belongs in an attribute**
(`role`, `variant`, `provenance`, `direction`), not in `type`. See
**[Gotchas, Do's & Don'ts](../resources/gotchas.md)**.

> **Now caught for you.** The current builder rewrites a `type: "heading"` node into this form before every compile
> (`encodeBindings`), so the builder no longer throws. Outside the builder — the SDK's `compile()`, or JSON an AI wrote —
> it still does. Run `checkNode(tree, { authoring: true })` (SDK 0.10.1+, or the MCP `chainrender_check_node` tool)
> first: it reports a bad `type`, along with every other field `compile()` would silently drop, such as a `role` that
> isn't in the dictionary. Every allowed field and value is on **[Node Fields](../reference/node-fields.md)**.

---

## 3. Make sure the builder knows the resolver kinds

`settle-*` and `trust-facets` must appear in the slot's **resolver kind** picker. If they don't,
your builder's embedded vocabulary is an older snapshot. Regenerate it from the one source of
truth and rebuild the site:

```bash
cd /h/chainrender_deploy/chainrender-project
npx tsx scripts/sync-builder-vocab.mjs     # VOCAB.resolverKinds <- chainc/resolverkinds.ts (edits only that array)
# then your normal site build regenerates builder/index.html from builder/index-next.html
```

Confirm the kinds are in the source of truth first:

```bash
npx tsx -e "import('./chainc/resolverkinds.js').then(m=>console.log(m.RESOLVER_KINDS.map(k=>k.kind).join(' ')))" \
  | tr ' ' '\n' | grep -E 'settle-|trust-facets'
```

---

## 4. Preview, and read the fail-closed state

Hit **Compile / Preview**. With the fix from step 2, compile succeeds and the builder calls the
interpreter's `render(bytes)` via `eth_call`, using the **Interpreter address + RPC** in the
config bar (the gear). Two honest outcomes:

- **Interpreter/RPC not set** → now you get a *real* interpreter/RPC error (this time the
  builder's hint is right). Set a valid interpreter for your chain and an RPC URL, then retry.
- **Set, but placeholder addresses** → the layout, headings, and the **grade badge** render, and
  each slot shows **"unavailable."** That is the design: a Grade-A read **fails closed** rather
  than inventing a value. Nothing is wrong — you simply haven't pointed it at a real agreement
  yet.

> Why "unavailable" and not an error? `settle-status` is a view read. With no real agreement at
> the address, there's nothing to read, so the honest render is "unavailable." See
> **[The Floor](../concepts/the-floor.md)**.

---

## 5. Point it at a real agreement (deploy + seed)

To see live numbers, deploy the stateless resolvers once per chain and a real agreement to read.
These contracts are **ownerless and immutable** — additive, no migration. Never put your
deployer key in shell history:

```bash
read -rs DEPLOYER_PK ; export DEPLOYER_PK    # hidden input; or: npx hardhat vars set DEPLOYER_PK
echo
# deploy SettlementCardResolver + TrustDisclosureResolver to your target chain (see the deploy guide
# for the exact script/flags for your setup), then deploy/seed an AttestedLoanChannel (or any
# ISettlementView) so there is real state to read.
```

Then, in the builder, replace **`0x1111…1111`** in all four slots with your deployed agreement
address, set `assetDecimals` to the escrow asset (6 = USDC, 18 = native), and preview again. The
status word, principal, and repaid-percent now render from chain, and `trust-facets` shows the
agreement's declared custody / exit / relayer-binding.

See **[Deploying an App](../guides/deploying.md)** and **[Reading On-Chain](../contracts/reading.md)**
for the per-chain specifics.

---

## 6. Confirm the grade

Check the grade badge: it should read **Grade A** — every slot is a `READ_CHAIN` resolver with
an on-chain data edge. If you add an off-chain bind later (an event feed, a client fetch), the
badge drops to ≤ C **by construction** and the honest label appears. That guard is the point:
the badge can never over-claim.

You've now done the full loop — import → fix → preview → deploy → live → grade. The next two
tours reuse this spine.

**Next:** **[Proof & Compute](./proof-and-compute.md)** →
