# Concepts

## The problem

A designer hands over a UI. A developer integrates real data into it. Somewhere in
that integration, a margin gets nudged, a colour is overridden "just for the error
state", a class is added to make a value fit. None of it is malicious and all of it
is invisible in review, because the diff looks like integration work. The design that
shipped is not the design that was approved.

The usual answers are process answers: review discipline, a design system, a linter.
This package takes a different one — it removes the capability. Integration is given
a vocabulary that *cannot express* a visual change, and a build gate that fails if
anyone reaches outside it.

## The drop

A **drop** is a folder the designer owns end to end:

```
wallet-stage-v1/
├── AGENTS.md           # the authoring contract, incl. the `contract:` version line
├── stage.html          # the fragment — no <html>/<head>/<body>; it mounts into a host element
├── stage.css           # all appearance; integration never opens this file
├── views/*.view.js     # one module per collection-shaped view
├── assets/             # fonts, icons — shipped and hashed with the drop
└── tools/              # the designer's own preview harness and per-state goldens
```

The product renders those files **byte-for-byte**. Every file is hashed at adoption,
and `verify` fails the build if a single byte changes. There is no "small fix in the
drop" — a change to the drop is a new drop.

Drops are self-contained by rule: no external URLs, no CDN fonts, no network calls.
Every asset ships inside the folder and is hashed with it.

## Slots

A **slot** is a named hole in the design where a runtime value goes. The designer
declares slots in their own markup, as `data-bind*` attributes, and the literal they
write in that position *is* the fixture:

```html
<span class="kpi" data-bind="wallet.summary.total">1,204</span>
```

Two consequences fall out of that one line:

- **The drop renders standalone.** With no data attached, it shows `1,204`. The
  designer's preview harness works without the product, and the fixture is a
  shape-by-example for whoever wires the data.
- **The slot name is the design's.** Namespaced to their surface, not to your kernel.
  The `project` functions conform your data to their names; the drop never renames to
  match yours.

If a real instance of the UI would show something else in that position, it is not
markup — it is a slot. That is the drop-authoring rule, and it is the designer's
responsibility to apply it before handing anything over.

## The seam

The **seam** is the runtime instance that connects a data snapshot to a mounted drop.
It is deliberately thin, and instance-scoped rather than global, so two stages on one
page can each carry their own.

```
kernel data ──project()──▶ snapshot ──seam.update()──▶ applyBindings ──▶ the drop's DOM
                                     └─seam.feedView()─▶ view module ──▶ a data-view-slot
```

Two paths in, and they have different trust properties:

- **`update(snapshot)`** runs the scalar bindings through the applier. This is the
  narrow path — three write forms, seven attributes, nothing else.
- **`feedView(id, data)`** hands data to a **view module** the *designer* wrote, and
  writes that module's returned string into the DOM. This is design-owned markup
  produced by design-owned code — the same trust boundary as the drop rendering its
  own fixtures. Integration supplies the data; it never supplies the markup.

## The closed vocabulary is a mechanism, not a rule

The applier is the least capable module in the codebase, on purpose. Its entire
surface is a `switch` over three cases:

| Form | What it does | What it cannot do |
| --- | --- | --- |
| `text` | sets `textContent` | emit markup — `innerHTML` is never used |
| `attr` | `setAttribute`, from a seven-name allowlist | write `class`, `style` or `id` |
| `presence` | toggles a state attribute the design declared | invent a state the design didn't style |

There is no branch that writes `className`, `style` or `classList`, and none that
creates or removes an element. **That absence is the mechanism.** "Integration cannot
change the design" is a property of the code, not a rule somebody has to remember in
review.

It follows that widening the vocabulary — adding an attribute to the allowlist,
adding a verb to the applier — is not a feature request. It is a request to remove
the only thing the package does. When a value genuinely doesn't fit,
[the answer is a view module or a slot the drop declares](write-vocabulary.md#what-the-vocabulary-cannot-reach),
never a new verb.

The corollary lands on the designer: **any state that changes appearance must be a
state they declared and styled.** Status colours, stale dimming, collapsed panels —
all of it keys off attributes the drop owns (`[data-state="stale"]`). If the designer
did not design a state, it cannot exist at runtime.

## The generated files are not yours

Adoption produces two generated files in your repo. Both are derived, both are
committed, and neither is editable:

- **The manifest** (`manifestOut`) records the sha256 of every drop file. It changes
  only when a new drop is adopted.
- **The bindings table** (`bindingsOut`) is scanned out of the drop's own `data-bind*`
  attributes. Anchors are the literal attribute tokens, so a designer renaming a slot
  fails `verify` at adoption rather than silently blanking a value in production.

`verify`'s *freshness* check re-runs the generator and compares byte-for-byte, so a
hand-edit is caught rather than obeyed. And regenerating the manifest to silence a
failing gate is the one canonical misuse of `pin` — it launders an edit to a
design-owned file into a pinned state. The generated file's own header says so.

## Two hash layers, two lifecycles

There are two integrity mechanisms in play and conflating them causes real confusion.
They gate different things at different times.

| | Seam manifest (`manifestOut`, from `pin`) | App manifest (`ui.integrity`) |
| --- | --- | --- |
| Records | what **design** shipped | what **this build** ships |
| Changes | only at adoption | on every build |
| Gates | the source tree — `verify` fails the build | delivery, and the shell's cache verification on load |

`verify` runs **before** stamping. That ordering is the point: a tampered drop cannot
launder itself into a validly-stamped package, because the build fails at the seam
gate first, before `ui.integrity` is ever recomputed over the tampered bytes.

## The contract version

The drop's `AGENTS.md` opens with a `contract: N` line naming the write vocabulary it
was authored against. `verify` reads it and fails if `N` is newer than the installed
CLI supports.

This exists for skew: a designer briefed on a newer vocabulary would otherwise hand a
drop to an older CLI, which would scan it with an incomplete understanding and bind
it wrongly — quietly. The gate makes that loud. The current contract version is **1**.

`AGENTS.md` is excluded from hashing by default (`notDropContent`), so it can be
carried forward across drops without breaking integrity.

## The two roles

The framework is a gate plus one guide per side of the seam. Neither guide is
optional:

- The **designer** works from the `drop-authoring` skill and, inside the drop
  itself, `templates/AGENTS.stage-drop.md` — what a slot is, the three forms, when
  something must be a view module, the five fixture states, self-containment. The
  skill adds the part a contract can't state: the *order* that avoids rework.
- The **integrator** works from the `node-design-seam` skill — the adopt/wire/verify
  runbook, the hard rules, and a decoder for every gate failure.

They are halves of one contract. A designer told "charts are view modules" is only
helped if the integrator is told "don't add a style verb — ask for a view module."

The split matters most at the point where the vocabulary runs out. An integrator
meets that limit at the wall, with a layout already drawn and a rebuild ahead. An
author meets it on a blank page, where it costs nothing — which is why the
authoring skill puts that decision first.
