# Changelog

All notable changes to `@econ-v1/node-design-seam`.

`0.1.0` and `0.1.2` were tagged in git but never reached the registry; their
contents shipped in `0.1.1` and `0.1.3`. The published line is
**0.1.1 → 0.1.3 → 0.1.4 → 0.1.5 → 0.1.6 → 0.1.7 → 0.1.8**.

## Read this first if you are not on 0.1.7 or later

Three of these releases fixed failures that produce **no error output at all**.
In every case `verify` exited 0 and told you everything was fine. None of them
will announce itself — you have to look.

| If you installed | What is silently broken | How to tell |
| --- | --- | --- |
| **0.1.1** | The installed CLI does nothing. Run through `node_modules/.bin/`, it exits 0 without executing the command — so your `verify` gate in CI passes while checking nothing. Fixed in 0.1.3. | Run `npx node-design-seam` with no arguments. It should print a usage line and exit 1. If it prints nothing and exits 0, your gate is dead. |
| **0.1.1, 0.1.3, 0.1.4** | Any element whose `data-bind-attr` carries more than one comma-separated `attr:slot` pair had **all** of those attribute writes killed, and `verify` still reported full coverage. Fixed in 0.1.5. | `grep 'source: "[^"]*,' <your bindingsOut>` — a generated `source` containing a comma is the signature. That element is frozen at its fixture value in production. |
| **0.1.1 – 0.1.5** | A slot whose dotted path is a prefix of another slot's (`a.b` alongside `a.b.state`) could not be resolved at all. The binding stayed at its fixture value. Fixed in 0.1.6. | Look for two declared slots where one path starts with the other. `seam.update()` returns the unresolved one in `missingSources` at runtime. |

Upgrading:

```sh
npm i -D @econ-v1/node-design-seam@^0.1.8 && npx node-design-seam bindings && npx node-design-seam verify
```

Expect `verify` to fail on the first run after upgrading from ≤0.1.4 if your
drop uses a multi-pair `data-bind-attr`. That is the fix working: slots that
were invisible to the old scanner now appear, unbound. Wire them — they were
never being written. See the `contract` and `freshness` rows of the failure
table in `skill/SKILL.md`.

## 0.1.8 — 2026-08-12

### Added

- **Published TypeScript declarations.** The package shipped plain checked JS with
  JSDoc types and no `.d.ts`/`types` field, so its first real TypeScript consumer
  (`node-app-observability`'s stage) had to import it behind
  `// @ts-expect-error -- no .d.ts published`, which hides real type errors at
  exactly the seam this package exists to make safe. `npm run build`
  (`tsc --declaration --emitDeclarationOnly`, `tsconfig.build.json`, scoped to
  `src/**/*.js` minus tests) now generates `types/*.d.ts` from the same JSDoc
  `checkJs` already type-checks; `prepack` runs it automatically so `npm pack` /
  `npm publish` — and a registry install — always ship a current tree. `types/` is
  gitignored, not committed: the JSDoc in `src/` stays the single source of truth.
  `package.json#exports` maps the `"types"` condition for both the package root and
  the `./src/*.js` subpath so each resolves under `moduleResolution: "NodeNext"`.
  Verified by installing a real `npm pack` tarball into a scratch TypeScript
  project: `createSeam`, `mountDropFragment`, `wireStage`, `applyBindings`, and
  `import type { Binding }` all resolve with no suppression — `Binding` matters
  beyond hand-written imports, since it's what every generated `bindings.gen.js`
  imports.

- **`node-design-seam preview` — a drop harness that cannot drift from the
  runtime.** Serves the drop at `http://127.0.0.1:4321` with a fixture-state ×
  theme × width toolbar, from a page that imports the real `mountDropFragment` and
  `createSeam` and contains no binder of its own.

  Until now the package shipped `renderFixtures()` and nothing else, so every drop
  hand-wrote its harness and each one re-implemented the write vocabulary from
  scratch. A harness that disagrees with `src/apply.js` produces a golden that
  agrees with *the harness* — so the pixel comparison, which is the designer's
  entire acceptance surface, silently stops describing the product. The first drop's
  harness already diverges on one edge: it renders an element hidden when its
  presence slot is absent from the fixture, where the applier skips an absent source
  and leaves the element as authored.

  Bindings come from the same scan `bindings` and `verify` run, so a drop can be
  previewed before it has ever been adopted — the state a designer is actually in.
  `--drop <path>` runs with no config file at all; `--port` and `--html` are also
  accepted. Nothing is written into the drop, so `pin`'s hashes still hold and
  `verify` still passes while a preview is open. Serving is allowlist-driven off the
  drop walk and bound to `127.0.0.1`.

  A drop may ship an optional `tools/preview-states.js` exporting `states` (one
  snapshot per fixture state, in the shape `seam.update()` takes) and optionally
  `themes` (merged over the selected state). View modules need no setup — each is
  fed its own `fixtures[state]`, and a module missing that state is named in a
  status line rather than rendered misleadingly.

- **A `docs/` tree** — getting-started, concepts, write-vocabulary, configuration,
  CLI, runtime API and troubleshooting, cross-linked from `docs/README.md` and
  shipped inside the tarball. The README becomes a front door pointing into it;
  depth moved rather than being duplicated, so there is one source of truth per
  topic.
- **This changelog**, and a `CONTRIBUTING.md` covering the four gates, the rules a
  contributor must not break, how to change the pack manifest deliberately, and the
  tag-driven OIDC release process.

### Changed

- **Licence: `MIT OR Apache-2.0` → `AGPL-3.0-only`**, with the full licence text
  added as `LICENSE` (the repository previously declared a licence in
  `package.json` but shipped no licence file). Releases 0.1.1 through 0.1.7 were
  published under the permissive terms and that grant still stands for them.
- `skill/SKILL.md`: the failure table's `contract` row is split in two. `verify`
  emits **scan errors** under that same prefix, and the table described only
  coverage mismatches — a scan error is a drop bug the integrator cannot fix,
  which calls for the opposite response.

## 0.1.7 — 2026-08-12

Pairs the 0.1.6 fix with the drop-authoring skill added just before it. No
runtime change of its own.

## 0.1.6 — 2026-08-12

### Fixed

- **A slot whose path is a prefix of another slot's could not be resolved, and
  silently kept its fixture value.** Nothing in the contract stops a design from
  declaring both `obs.health.errorRate` (a KPI's text) and
  `obs.health.errorRate.state` (that KPI's state attribute) — and the first drop
  authored against this contract does exactly that.

  A nested snapshot cannot express both: the string sitting at `errorRate` blocks
  the walk to `.state`, so `readSource` returned `undefined` and the applier left
  the attribute as the designer shipped it. There was no projection shape that
  satisfied the pair. The only remaining options were asking design to rename a
  slot — but the contract says design names slots — or accepting a dead attribute.

  `readSource` now falls back to the whole dotted path as a single literal key
  when the nested walk is blocked or finds nothing, so
  `{ "obs.health.errorRate.state": "elevated" }` resolves. **A nested value still
  wins where both exist**, so no existing snapshot changes meaning.

### Added

- **A `drop-authoring` skill** (`skills/drop-authoring/SKILL.md`) — the design side
  of the loop, which nothing covered. It answers the question an agent is handed
  first: "add a card to that screen" — whether that is a drop change at all, and
  what has to be decided before anything is drawn. Left unanswered, the default is
  to build the surface in the consuming app's code, which is the one failure a
  hash-pinned drop exists to prevent.
- Both skills now route to each other by description, so an agent asked for design
  work is pulled toward the authoring skill rather than the integrator's — whose
  "never edit a file under the drop root" reads as a prohibition on precisely what
  the author's job is.

## 0.1.5 — 2026-08-12

### Fixed

- **A multi-pair `data-bind-attr` silently killed every write on that element,
  and `verify` reported full coverage anyway.** The scanner's regex captured
  greedily past the first colon, so
  `data-bind-attr="data-state:obs.a, data-count:obs.b, aria-valuenow:obs.c"`
  scanned as *one* binding: attribute `data-state`, source
  `obs.a, data-count:obs.b, aria-valuenow:obs.c`. No snapshot ever supplies a
  slot by that name, so nothing was written and the element kept the literal the
  designer shipped. The other two pairs were never scanned at all.

  `verify` could not catch it: the declared-slot list and the bindings list are
  both derived from that same scan, so they agreed with each other and coverage
  read 100%. Found by the first real drop, where it had frozen a forward-queue
  meter at its fixture value.

  Each comma-separated pair is now scanned, validated and emitted independently.
  A malformed pair (no colon, or an empty attribute or slot) is a scan error that
  refuses to write, rather than a silently dropped binding. All pairs on one
  element share the original attribute token as their anchor, so the emitted
  bindings still agree on which element to write into.

  No vocabulary change: `contract:` stays at **1**. Several attribute writes on
  one element was always allowed; only the scanner failed to read it.

## 0.1.4 — 2026-08-12

### Documentation

The template said what the write vocabulary *is* and never what to do when it
does not reach. Both gaps had already cost a redesign on the first real drop
because they surface late. `templates/AGENTS.stage-drop.md` now names the answer
up front, and `skill/SKILL.md` carries the integrator-side mirror — a table of
the three requests that look like a missing verb, why none of them are, and the
sanctioned alternative.

- **Continuous geometry is a view module, not a bound scalar.** A bar's width, a
  sparkline's path, a gauge's arc — none of it can be delivered as text or as one
  of the seven allowlisted attributes. Every chart is a view module; decide that
  before laying the surface out.
- **A value carrying structure *inside* its text is a view module too.** A
  redaction marker in a log line, a highlighted match, a unit suffix styled
  differently — `data-bind` sets `textContent` and by design cannot emit markup,
  so it prints raw.
- **Theme arrives as data, not environment.** A drop mounts inside a shadow root
  and inherits nothing, so no CSS-only mechanism reaches it, and
  `prefers-color-scheme` follows the OS rather than the host's theme control and
  desyncs the moment someone overrides it. Design every theme and key them off a
  state slot on a wrapper the drop owns.

No code change: the allowlist is untouched, `contract:` stays at **1**, and the
tarball ships the same files.

## 0.1.3 — 2026-08-11

### Fixed

- **The installed CLI was a silent no-op.** Invoked through the
  `node_modules/.bin/node-design-seam` symlink npm creates, the entry guard
  compared `import.meta.url` against the *symlink* path in `argv[1]` while
  `import.meta.url` is the real file's URL. They never matched, so the dispatcher
  never ran: every installed invocation exited 0 having done nothing. A
  consumer's `verify` CI gate passed while checking nothing — no output, no
  error, a green build over an unverified drop. `argv[1]` is now resolved through
  `realpathSync` before the comparison, and a regression test drives the bin
  through a symlink.

  Tagged as `v0.1.2`, but that tag never published; the fix reached npm here.

### Changed

- First release published tokenless, via npm OIDC trusted publishing — no
  long-lived token in CI, and provenance is generated automatically.
- The tarball file-list gate (`npm run verify:pack`) now parses `npm pack
  --dry-run --json` across npm's output shapes, and the publish workflow pins
  npm to `@11`. The gate had been failing on output-shape drift.

## 0.1.1 — 2026-08-11

First release on npm. `v0.1.0` was tagged but never published.

### Fixed

- **Generated `bindings.gen.js` did not type-check in a consuming repo.** The
  emitted `@import { Binding }` header pointed at a subpath the package's
  `exports` map did not expose, so under strict NodeNext the import resolved to
  nothing. The `Binding` typedef is re-exported from the package root, the
  emitted header points there, and a `./src/*.js` subpath export was added as
  belt-and-braces. Proven with a real `npm pack` plus a strict-checkJs consumer.
- **A `data-bind` token in a non-markup file minted a phantom slot.** The scalar
  scan now runs over `.html` only, so a `data-bind` string appearing in
  `stage.css` (or anywhere else) no longer declares a slot that nothing can bind.
- **A slot name containing a quote character shipped broken generated code.** The
  scan rejects it now, with an error, instead of emitting a file with a string
  literal broken out of.
- **`wireStage` leaked subscriptions when `ctx.liveQuery` threw partway through
  wiring.** Subscriptions already created are disposed before the error is
  rethrown.
- Runtime error strings said `design-shell:`; they say `node-design-seam:`.

### Added

- **`legacyAnchors` config entries are validated at load.** Each must be an
  object whose `anchor` is attribute-selector-shaped (`[name="value"]`), with a
  string `source` and an object `as`; `loadConfig` throws naming the offending
  index otherwise. This is not stylistic: `verify`'s anchor check is a plain
  substring test against the drop's text, so anything more expressive than an
  attribute selector — a class selector, a descendant combinator, `:hover` —
  could never be found there, and would have passed as a binding that resolves
  to nothing.

## 0.1.0 — 2026-08-11 (tagged, never published)

Initial implementation. Reached npm as part of 0.1.1.

### Added

- **Runtime.** `mountDropFragment` (hosts a drop's own HTML/CSS bytes verbatim,
  in a shadow root by default), `createSeam` (instance-scoped `update` /
  `feedView` / `renderFixtures` / `dispose`, with a capped MutationObserver that
  re-binds after a late design render), `wireStage` (optional `ctx.liveQuery`
  sugar returning one disposer), and `applyBindings` with the exported
  `ALLOWED_ATTRIBUTES` set.
- **The closed write vocabulary**, extracted verbatim from `design-shell`: three
  forms — `text`, `attr`, `presence` — and a seven-attribute allowlist
  (`aria-label`, `aria-valuenow`, `data-count`, `data-state`, `hidden`, `href`,
  `title`). No branch writes `class`, `style` or `id`, and none creates or
  removes an element.
- **CLI** — `pin`, `bindings`, `verify`, `adopt`, over one
  `node-design-seam.config.json` per app repo.
- **`verify`**: integrity, anchors, contract coverage, freshness, plus the
  contract-version gate that fails a drop authored against a newer vocabulary
  than the installed CLI supports.
- **Teaching layer** — README, the `node-design-seam` integrator skill, and the
  `AGENTS.stage-drop.md` design-side template.
- **A self-referential demo app** under `fixtures/`: this repo's own CI runs
  `verify` against its own drop.
