# Getting started

The loop is: **adopt** a drop, **wire** a stage entry to it, **verify** in CI. This
page runs it end to end. It assumes a designer has handed you a drop folder — if you
are the one authoring the drop, read `templates/AGENTS.stage-drop.md` instead.

## 1. Install

```sh
npm i -D @econ-v1/node-design-seam
```

That installs the `node-design-seam` bin, the runtime, and its `.d.ts`
declarations — a TypeScript stage entry imports the package with no
`@ts-expect-error`. Node 18 or newer.

## 2. Configure

One `node-design-seam.config.json` per app repo, at its root. Three keys are
required; everything else defaults:

```json
{
  "dropRoot": "design/wallet-stage-v1",
  "manifestOut": "ui/src/design/drop-manifest.js",
  "bindingsOut": "ui/src/design/bindings.gen.js"
}
```

All paths resolve relative to the config file itself, not the working directory.
Full reference: [Configuration](configuration.md).

## 3. Adopt the drop

Place the designer's folder under your `design/` directory and generate against it:

```sh
npx node-design-seam adopt ../inbox/wallet-stage-v1
```

`adopt` copies the folder into place next to the current `dropRoot`, carries the
previous drop's `AGENTS.md` forward if the incoming one shipped without it, re-points
the config, then runs `pin` and `bindings`. It finishes by printing the **contract
diff** — the slots added and removed since the previous drop:

```
adopt: wallet-stage-v1 in place.
  contract diff — added: [wallet.summary.total] removed: []
  next: wire added slots (or record in unwired), delete bindings for removed ones, run verify.
```

Read that diff. It is the only place the change in the data contract is stated
plainly.

This writes two generated files, both of which belong in version control:

- **`manifestOut`** — the sha256 of every file in the drop. This is what makes the
  drop hash-pinned.
- **`bindingsOut`** — the binding table, scanned out of the drop's own `data-bind*`
  attributes.

Never hand-edit either one. See [Concepts](concepts.md#the-generated-files-are-not-yours).

## 4. Wire the stage entry

The stage entry is developer-owned code that mounts the drop's bytes and feeds them
data. Lifecycle-pair it to `mount(ctx)` / `unmount()` — never fire-and-forget:

```js
// ui/src/main.js — the stage entry (developer-owned)
import { createSeam, mountDropFragment, wireStage } from "@econ-v1/node-design-seam";
import BINDINGS from "./design/bindings.gen.js";
import cards from "../../design/wallet-stage-v1/views/cards.view.js";
import DROP_HTML from "../../design/wallet-stage-v1/stage.html?raw";
import DROP_CSS from "../../design/wallet-stage-v1/stage.css?raw";

let seam, unwire;

export async function mount(ctx) {
  const { root } = mountDropFragment({ host: ctx.host.root, html: DROP_HTML, css: [DROP_CSS] });
  seam = createSeam({ root, bindings: BINDINGS, views: [cards] });
  unwire = wireStage(seam, ctx, {
    snapshot: { query: "wallet.summary.v1", params: {}, project: toSummarySnapshot },
    views: { "wallet.cards": { query: "wallet.cards.v1", params: {}, project: toCardsModel } },
  });
}

export async function unmount() {
  unwire?.();
  seam?.dispose();
}
```

The `?raw` suffix is Vite's — the point is that the drop's HTML and CSS arrive as
**strings at build time** and are mounted verbatim. Use whatever your bundler offers.

Three things are doing work here:

1. **`mountDropFragment`** hosts the drop's own bytes, by default inside a shadow
   root so the drop's CSS and the shell's skin cannot reach each other.
2. **`createSeam`** returns an instance scoped to that mounted root.
   `update(snapshot)` drives the scalar bindings; `feedView(id, data)` re-renders a
   view module into the element carrying `data-view-slot="<id>"`.
3. **`wireStage`** is optional sugar — one `ctx.liveQuery` subscription per wiring
   entry, each result run through that entry's `project` and pushed into the seam. It
   returns a disposer, which is what `unmount()` must call.

Skip `wireStage` entirely if your app has bespoke subscription needs and call
`seam.update` / `seam.feedView` from your own callbacks.

Full signatures: [Runtime API](api.md).

### Writing `project` functions

`project` is the kernel→slot mapping, and it is the developer's job. It conforms your
kernel view-models to the **design's** slot names:

```js
const toSummarySnapshot = (rows) => ({
  wallet: { summary: { total: formatSats(rows.totalSats) } },
});
```

Two rules:

- **Return plain data, never markup.** `feedView` writes a view module's returned
  string straight into the DOM. That string is design-owned output; a `project`
  returning markup would smuggle integration-authored HTML across the boundary.
- **Conform to the design's names.** The drop never renames its slots to match your
  kernel. The mapping is yours to write and yours to maintain.

The snapshot is read by dotted path, so nest it to match the slot name:
`wallet.summary.total` reads `snapshot.wallet.summary.total`. A slot name also
resolves as a single flat key when nesting can't express it — see
[how a source is resolved](api.md#how-a-source-is-resolved).

## 5. Gate the build

`verify` is the CI gate. Wire it into the build so a broken seam cannot ship:

```json
"scripts": {
  "build": "vite build && node-design-seam verify",
  "verify:design": "node-design-seam verify"
}
```

A passing run looks like this:

```
verify: contract coverage 3/3 slots bound; views: wallet.cards
verify: PASS (integrity, anchors, contract, freshness)
```

Coverage is printed on **every** run, passing or not. A drop that declares a slot you
have not wired is loud, not silent.

> **Check your gate is real.** In version 0.1.1 the installed bin exited 0 without
> running the command, so a `verify` step passed while checking nothing. Run
> `npx node-design-seam` with no arguments: it must print a usage line and exit 1.
> If it prints nothing, upgrade — see the [CHANGELOG](../CHANGELOG.md).

## 6. Compare against the golden

A green `verify` proves the wiring is *legal*. It does not prove it looks right.
The drop ships its own preview harness and per-fixture-state goldens under `tools/`;
compare the mounted stage against them. Only the visual comparison catches a value
that is bound correctly and formatted wrongly.

## Next

- Something failed → [Troubleshooting](troubleshooting.md)
- A value doesn't fit any of the three forms → [Write vocabulary](write-vocabulary.md#what-the-vocabulary-cannot-reach)
- You want the whole model → [Concepts](concepts.md)
