# Troubleshooting

Every `verify` failure line is prefixed with the name of the check that produced it.
Find the prefix below.

**None of these are fixed by editing the drop.** If the drop is genuinely wrong, that
is a design change — escalate it, don't patch it.

## `integrity:`

```
FAIL integrity: modified design file: stage.css
FAIL integrity: added to drop (not in manifest): views/new.view.js
FAIL integrity: removed from drop: assets/icon.svg
```

**Means:** the drop's bytes no longer match what `pin` recorded. Either someone edited
a file under the drop root, or a new drop arrived without going through `adopt`.

**Do:** if the drop wasn't meant to change, revert the edit — `git checkout` the drop
root. If a new drop really did arrive, adopt it properly:
`node-design-seam adopt <path>`.

**Don't:** re-run `pin`. That is the one canonical misuse — it launders the edit into
a pinned state and the gate never fires again.

## `anchors:`

```
FAIL anchors: binding for "wallet.summary.total" matches nothing in the drop (anchor [data-bind="wallet.summary.total"])
```

**Means:** a binding's anchor token is not present in the drop's text. The drop's own
contract changed — a slot was renamed or removed — or a `legacyAnchors` entry points
at markup that no longer exists.

**Do:** re-run `node-design-seam bindings` and reconcile the diff with design. If the
slot is genuinely gone, the wiring that fed it is now dead code; remove it.

**Don't:** hand-edit the selector or repoint it at a nearby element. An anchor is the
design's own attribute token, not a selector you choose.

## `contract:`

This prefix covers two different things.

### Scan errors

```
FAIL contract: stage.html: data-bind-attr uses "class" — not in the allowlist [...]
FAIL contract: stage.html: data-bind-attr pair "data-count:" is malformed — both attr and slot must be non-empty
```

**Means:** the drop's markup can't be read. The full list is in
[Write vocabulary](write-vocabulary.md#scan-errors).

**Do:** this is a drop bug — the designer wrote something the vocabulary doesn't
allow, or a malformed pair (a trailing comma is the usual cause). File an Ask. You
cannot fix it on your side, and `bindings` will refuse to write the table until it is
fixed.

### Coverage mismatch

```
FAIL contract: declared slot "wallet.summary.pending" is neither bound nor listed in unwired — wire it or record the debt
FAIL contract: binding source "wallet.summary.legacyTotal" targets no declared slot in the drop
```

**Means:** the drop and the binding table disagree about which slots exist.

**Do:** for a declared-but-unbound slot, wire it — finish the runbook's wiring step.
If you are knowingly deferring it, add it to the config's
[`unwired`](configuration.md#unwired) list so the coverage report
stays honest about the debt. For a binding targeting a slot the drop no longer
declares, remove the binding *and* the wiring behind it.

**Don't:** delete a binding just to reach green unless the slot is genuinely gone, and
don't park a slot in `unwired` to skip work you were about to do.

**If this appeared right after upgrading the package**, read
[After a package upgrade](#after-a-package-upgrade) first — the correct response is
different.

## `freshness:`

```
FAIL freshness: committed bindings table differs from a regeneration — never hand-edit bindings.gen.js; run `node-design-seam bindings`
```

**Means:** re-running the generator produces different bytes than what is committed.
Almost always a hand-edit of generated output.

**Do:** run `node-design-seam bindings` and commit the result.

**Don't:** hand-edit `bindingsOut`. Edits there are invisible to the generator and
will be clobbered — you have both wasted the edit and hidden the intent behind it.

**If nothing was edited and the drop didn't change**, you upgraded the package —
see [below](#after-a-package-upgrade).

## `contract-version:`

```
FAIL contract-version: drop declares contract 2; this CLI supports <= 1 — upgrade @econ-v1/node-design-seam
```

**Means:** the drop was authored against a newer write vocabulary than the installed
CLI understands.

**Do:** upgrade `@econ-v1/node-design-seam`.

**Don't:** wire the drop against the older vocabulary, and never edit the drop's
`contract:` line to make the gate pass. That line is the whole point of the gate:
without it, an older CLI scans a newer drop with an incomplete understanding and
binds it wrongly, quietly.

---

## After a package upgrade

A scanner fix changes what the drop is *understood to declare*, without a byte of the
drop changing. The signature is distinctive:

- `integrity` **passes** — the hashes are untouched.
- `freshness` and `contract` both **fail**.
- Regenerating produces a diff with slots you have never seen.

That combination is the fix landing, not a regression.

### Upgrading across 0.1.5

This happens to any drop that puts several comma-separated pairs on one
`data-bind-attr`. Before 0.1.5 the scanner read the whole attribute value as a
**single** binding whose source was the rest of the string verbatim —
`obs.a, data-count:obs.b, aria-valuenow:obs.c`. No snapshot supplies a slot by that
name, so every attribute write on that element was dead and the element kept the
designer's fixture literal in production.

`verify` reported full coverage throughout, because the declared-slot list and the
bindings list were both derived from that same misreading and therefore agreed with
each other.

When the new slots appear:

1. **Regenerate** — `node-design-seam bindings` — and read the diff as a contract diff.
2. **Treat every newly visible slot as unwired *and previously broken*,** not as new
   design work. Look at the element in the drop: whatever it renders today is the
   fixture value, frozen.
3. **Wire them.** Reaching for `unwired` here files a live defect on the surface as
   "tracked debt" — only use it if you would have deferred that slot anyway.
4. **Find it before upgrading:** a `source` containing a comma anywhere in a committed
   bindings table is the pre-0.1.5 signature.

   ```sh
   grep 'source: "[^"]*,' ui/src/design/bindings.gen.js
   ```

### Upgrading across 0.1.6

No gate changes and no regeneration needed — but a binding that was impossible to
satisfy becomes satisfiable. If your drop declares two slots where one path is a
prefix of the other (`a.b` and `a.b.state`), the deeper one could not be resolved
before 0.1.6: a nested snapshot puts a value at `a.b` that blocks the walk, so the
element silently kept its fixture. A slot name now also resolves as a flat key, so
emit `{ "a.b.state": … }` alongside the nested value and the write lands. See
[how a source is resolved](api.md#how-a-source-is-resolved).

Nothing reports this one, because the slot was declared *and* bound the whole time —
`verify` was always green. Check `missingSources` on the result of `seam.update()`.

### Upgrading across 0.1.3

Nothing in your repo changes, but your CI gate starts working. In 0.1.1 the installed
bin exited 0 without running the command, so `verify` steps passed while checking
nothing. If a previously green build now fails, the failures were always there — read
them as a first run, not a regression.

```sh
node-design-seam        # must print the usage line and exit 1
```

---

## Problems that don't show up as a `verify` failure

### A value never changes on screen

The binding is fine and the data isn't arriving. In order:

1. Check the return of `seam.update(snapshot)` — `missingSources` lists the dotted
   paths that weren't in the snapshot. A missing source leaves the design's fixture
   in place, which looks exactly like "nothing happened".
2. Check the shape of the snapshot against the slot name. `wallet.summary.total`
   reads `snapshot.wallet.summary.total`, so `project` must nest.
3. **Check whether the slot name is a prefix of another slot's.** If the drop
   declares both `a.b` and `a.b.state`, no nested object can carry both — the value
   at `a.b` blocks the walk. Emit the deeper one as a flat key:
   `{ "a.b.state": "elevated" }`. See
   [how a source is resolved](api.md#how-a-source-is-resolved).
4. Check that `unwire`/`dispose` aren't being called early by the stage lifecycle.

### `feedView` does nothing and warns

```
node-design-seam: feedView("wallet.cards") ignored — no [data-view-slot="wallet.cards"] in the fragment
node-design-seam: feedView("wallet.cards") ignored — no registered view module
```

Either the id isn't in the `views` array passed to `createSeam`, or the drop's markup
has no matching `data-view-slot`. The id must match the module's own `id` field
exactly — that attribute is the entire contract between the markup and the module.

### The drop renders unstyled, or ignores the app's theme

Expected. The drop mounts in a shadow root and inherits nothing: its own `stage.css`
must be passed to `mountDropFragment` via `css: [...]`, and the host's theme cannot
reach it. A multi-theme drop declares a theme slot you feed. See
[theme is data](write-vocabulary.md#theme-is-data-not-environment).

### The stage looks right but a value is formatted wrongly

`verify` proves the wiring is legal, not that it looks right. Compare against the
drop's own goldens under `tools/`. Formatting belongs in `project` — never in the
applier, and never by editing the drop.

### `bindings` reports an error and writes nothing

By design: the scan fails closed. Fix the drop-side error (or file an Ask — scan
errors are drop bugs) rather than working around it.

---

## When the drop seems to need a verb you don't have

Three requests come up repeatedly. None of them is a missing verb.

| The request | Why it isn't a new verb | Instead |
| --- | --- | --- |
| "This bar's width / chart's path has to come from data." | Continuous geometry isn't expressible as text or an attribute, and a `style` verb would hand integration full restyling power. | A view module. If the drop shipped the chart as static markup, that's a drop bug — file an Ask. |
| "The value has a highlight / redaction marker inside the string." | `data-bind` sets `textContent`; it cannot emit markup, which is what stops element injection. | A view module parses the structure. Feed the raw string. |
| "The drop must follow the host's light/dark theme." | Shadow root inherits nothing; `prefers-color-scheme` tracks the OS and desyncs on manual override. | Feed the theme into a state slot the drop declares. If it declares none, it supports one theme. |

The shared shape: the answer is either a **view module** (design's code, design's
markup) or a **slot the drop declares**. Never a new verb, and never a selector you
invent.

Escalate a design change as an Ask — in the node monorepo, `docs/design/asks/`; for
out-of-repo apps, the design engagement's agreed channel.
