Skip to content

Design system: identities, contracts, behaviour, models, derived variants, codegen and an example over @hwc/components - #36

Open
beharguy wants to merge 74 commits into
mainfrom
design-system
Open

beharguy wants to merge 74 commits into
mainfrom
design-system

Conversation

@beharguy

@beharguy beharguy commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Closes #37
Closes #38

Summary

A .uidx file becomes the identity of a component: intent, anatomy, layout and styles, plus a written contract, behaviour guidelines and view models. Components are renders of that identity — the viewer canvas, HTML/CSS and React here; Figma unchanged. Nothing in the file computes.

Documented in six ADRs, implemented end to end, with an example package rendering six identities over the headless @hwc/components library, and an inspector that edits all of it.

Decisions (docs/decisions)

  • 0012 Design-system model: identity + contract + behaviour guidelines; components are renders.
  • 0013 ## Contract: props, events, slots, form, accessibility, composes. implements binds a headless root, part binds its parts. <Prop sample> for demonstration values. <State> only for states the element produces itself; <Part> descriptions optional. The identity never spells a library: uidx.json may carry a profile and per-component bindings.
  • 0014 ## Behavior: short - id: sentence bullets. No when, no code.
  • 0015 ## Models and ## Examples: view models declared once, shared across pages, named by a prop's type; every field explained; samples for demonstration only.
  • 0016 Variants as renders: a <Styles> table keyed by visual enum props and states derives the set. Enum props are axes, visual booleans are states, hover/focus/active need no declaration. Derived variants are addressable: a state is designed on the canvas and its row is written.
  • 0017 Collections and code targets: repeat="{items}" on any layer of a component, with as, nested for trees; a repeat on a <Slot> is the consumer-fillable one and a render prop in React; slots and render props in React, compositions render the instance they hold, shadow parts through ::part(), codegen config shared by CLI and viewer.

Packages

  • format: the four regions, the <Styles> table, the repeat/as attributes, spec types, diagnostics UIDX140–152, the style and contract patch ops with inverses; contract-only helpers (axes, state kinds).
  • schema: derived variant sets, sample bindings, repeat expansion, derivedTarget, contractJson, auditDesignSystem with a shared model index; pass-through instance props resolve in the consumer's scope; slots hug; whole-attribute paint aliases compose strokes.
  • codegen (new): HTML/CSS and React 19 over headless custom elements, contract JSON, conformance against custom-elements.json, shadow parts, library profiles and bindings.
  • server: uidx.json gains headless (path or { manifest, profile, bindings }) and codegen (out, targets); routes serve the library, discover libraries from dependencies' customElements, write the choice, and generate code; the symbol lint reads contract props.
  • viewer: a Contract tab beside Design — implements and parts bound from either end, a Repeat block on any layer (list, item name), library picker, the contract itself editable with "Fill from library", a Generate button; derived variants selectable and editable on the canvas; a Repeat tool. A Models face beside Tokens and Fonts: every model of the document with its page and its users, edited in place, declared on a chosen page (a models page as the shared one), linked from a repeat's row; the new model and field patch ops write one <Model> back canonically.
  • From the tree walkthrough (validation run 2026-09-30): an instance inside a repeat shows what each contract prop receives, inferred by type from the enclosing repeats and overridable with props={{ node: '{child}' }} (the code target passes the same); a Bind picker beside a text's content, and a typed {…} written as a binding; undeclared slots listed with Declare; "repeat the row instead" when a repeat wraps one row; Make component warns when the page already has a component; new slots hug; Place instance fills the selected slot; the picker leaves out the enclosing component; an emptied ## Contract heading goes; uidx codegen takes directories. Fixed: the incremental patch path dropped a component's spec after structural edits, and uidx check flagged repeat="{items}" as an unknown property (now covered by a CLI test on the example).
  • From the second walkthrough (a tree row with a turning chevron): a visual prop draws its state set before any style row exists, so the first row comes from the canvas; rotation is a look a state may set; a use's bound value (props={{ open: '{item.expanded}' }}) chooses the row's state on the canvas as it does in code; the canvas now actually receives the shared model index (the binding had landed on the rail); a text bound to a boolean is spelled out instead of crashing the layout; stroked vectors stay stroked in codegen; the Repeat tool prefers the enclosing item's lists and names nested items (child); a sized component reads Free; bound instance props are not called mistyped.
  • For designers who never open the file: a page that cannot be drawn keeps its last frame and says why in a banner instead of going blank; a number field commits on Enter; declaring a prop offers its type, and a boolean comes with default={false}; a checkbox, a number and the visibility pill bind to an enclosing item's field (item.expanded) or a prop from a picker beside the control, as a text already did, and any {…} committed from the inspector is written as the binding it is. Verified live: binding a tree's children frame to item.expanded from the Appearance pill wrote visible="{item.expanded}" and hid the collapsed row's children on the canvas.
  • Designer UX review against Builder.io, Plasmic, Framer, Webflow, Figma and Penpot: text is typed where it is drawn (Text tool opens it, double-click reopens it, Escape/⌘Enter keeps it); a right-click menu with the toolbar's actions and shortcuts; "Saving…" / "All changes saved" in the status strip; an empty-page hint; the "Inside …" breadcrumb by current name; the Contract tab leads with properties and slots and folds code binding and code library under "for developers"; plain-words overview counts, file-error overlay and Models copy. Report with the peer bar: https://claude.ai/artifact/F4fuNuEcY8UPvuG5qHWtcZ
  • cli: uidx check audits the whole document; uidx contract; uidx codegen reads headless and codegen from uidx.json.
  • examples/design-system: Checkbox, Field, CheckboxField, Button, ContactOption, ContactList over @hwc/components, generated output committed with a drift test, a Vite demo page; uidx check passes on the whole document.

Verification

  • Every suite passes (format, schema, server, agent, viewer, cli, codegen, example); root lint, typecheck and prettier clean; the example's generated output has no drift.
  • Verified live in the viewer: the derived checkbox states, the button set with per-variant hover, the contact list's repeated rows; editing a hover state from the canvas wrote root:opacity into its style row; editing a prop's description from the Contract tab rewrote the <Prop>; the Generate button wrote the same 36 files the CLI writes.

🤖 Generated with Claude Code

Guy Behar and others added 8 commits September 29, 2026 23:46
Identity, contract, behaviour guidelines, models and examples, variants as
renders, collections and code targets. Theming beyond modes is recorded as a
future task.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A .uidx file may carry ## Contract, ## Behavior, ## Models and ## Examples
after the visual contract, and a <Styles> table beside the root. They are
lowered into doc.spec as declarations, never scene nodes; the tree the
viewer draws is unchanged. <Repeat slot count> multiplies one instance for
the design tools. The emitter prints the regions back verbatim so uidx fmt
keeps them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…system audit, uidx contract

A component with a <Styles> table is drawn as the variant set the table
derives, one synthetic tree per combination, unlinked from the file. A
contract prop with a model resolves {item.field} to the model's samples, and
a <Repeat> multiplies its instance at successive sample indexes. The audit
checks parts, slots, rows, axes, models, bindings and repeats against each
other, and uidx check runs it. uidx contract prints the spec as JSON.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…system over @hwc/components

@uidx/codegen renders each component identity to code (ADR 0017): a
stylesheet keyed by the headless root's attributes and states, a markup
fragment with sample content, a typed React component with props and events
from the contract — a plain slot as a ReactNode prop, a repeating slot as
items plus renderItem generic over the model, text parts as compound
sub-components, compositions as the instance they hold — plus models.ts,
runtime.ts, elements.d.ts and the contract as JSON. With a
custom-elements.json manifest, each contract is checked against its element.
`uidx codegen` drives it; `--check` fails on drift.

examples/design-system carries Checkbox, Field, CheckboxField, Button,
ContactOption and ContactList as .uidx identities over the vendored
@hwc/components build, with generated/ committed and tested for drift, a
Vite page mounting the HTML fragments beside the React components, and the
React output type-checked against React 19.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…gh props

Canvas fixes found by rendering the example end to end:

- A whole-attribute stroke alias (`strokes="{border#default}"`) skipped the
  stroke composition, so the stroke kept its colour and lost its weight and
  drew as nothing. Both paths now share `wholePaintAlias`.
- The derived component set no longer carries the component's own paint and
  layout; those live on each variant's root (ADR 0005 §5).
- A `<Slot>` that says nothing about its size hugs its placeholder instead of
  the engine's 100x100 box.
- `<Prop sample>`: a demonstration value for the canvas and generated markup
  (ADR 0013 amended); a text bound to a prop with neither sample nor default
  renders empty.
- An instance's `props={{ label: '{label}' }}` resolves in the consuming
  component's scope before entering the definition's, and a component
  declared by its contract alone accepts passed values. The HTML target does
  the same for compositions.
- `layoutAlign` maps to `align-self` in the CSS target.

Example: hug sizing declared on the hugging components, per-variant hover
rows for the button, samples on text props, light background for the demo.
Agent's insertable-element test now lists `Repeat`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ess library

The inspector gains a Contract tab beside Design (ADR 0013 §3 amended).
Figma's precedent: bindings live in the properties panel, declared on the
component and applied from the layer, with choices from a curated list.

- A component chooses the element it implements from the library's roots; a
  text field only when the document names no library.
- Each declared part is a row: bound rows name their layer and select it,
  unbound rows are a picker over the layers that could draw it. Binding from
  a new layer unbinds the old one in the same patch. A layer's own row offers
  the same parts, naming who holds the taken ones.
- A <Repeat> picks a declared repeating slot (reselecting itself at its new
  address) and its count. Slots show whether the tree provides them.
- The file's contract (props, events) is shown beside the bindings; the tab
  badge counts parts still to bind.

uidx.json gains "headless": the library's custom-elements.json, served on
/__uidx/headless and read on every document:opened; uidx codegen uses it
when --manifest is absent. Roots and parts follow the <root>-<part> naming
convention (plus cssParts), with a leaf test so hwc-text-input stays a root
beside hwc-text.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The manifest's own vocabulary for parts is `cssParts`: parts inside the
root's shadow tree, styled from outside through `::part()`. Until now every
part had to be a `<root>-<part>` element, which is one library's convention.

- The component model records each part's kind. A shadow part is styled as
  `root::part(name)` in the CSS target, emits no element in HTML or React,
  and gets no compound sub-component.
- Conformance accepts either kind. A shadow part with design content under
  it warns rather than fails: the library draws the part, so that content is
  styled but not rendered.
- The Contract tab marks shadow parts with a pill on the component's list
  and in the layer's picker, so an author knows it can be styled, not filled.

ADR 0017 §3 records the rule.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The conformance warning for design content under a shadow part is gone. The
library drawing the part, and the design drawing its own for the canvas and
Figma, is how a shadow-part library works; a warning nobody can act on is
noise. Conformance accepts a cssParts entry exactly as it accepts an
element. ADR 0017 §3 reworded.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Guy Behar and others added 9 commits September 30, 2026 12:51
The contract API, simplified to what each renderer actually reads:

- `<States structural styling>` is gone. A boolean prop marked `visual` is a
  state; `hover`, `focus` and `active` are the browser's and need no
  declaration; `<States><State name>` remains only for states the element
  produces itself, rendered as `:state(name)`. The state axis is default,
  the visual booleans in prop order, the interaction states in table order,
  then the declared ones.
- `<Parts>` is optional and written like every other list, `<Part name>`
  with a description. The tree's `part=` bindings are the declaration.
- A model is named by a prop's type — `Contact`, `Contact[]` — and shared
  across pages: declared once, on the page of the component that shows it.
  `model="{models#…}"` is gone; a repeating slot names the list prop it
  iterates, `of="items"`, so the model is written once. The example no
  longer declares Contact twice.
- The parser names each old spelling and says what replaced it.

The contract-only helpers (axes, state kinds) move to @uidx/format so the
server's symbol lint can read them: `componentProps` now includes contract
props, a `{label}` passed through an instance is not judged at the use, a
`{item.name}` binding resolves through the model prop, and a derived set
draws every combination. `uidx check` passes on the whole example for the
first time; the audit runs after every page is parsed with a shared model
index. The Contract tab marks visual booleans as states and lists declared
element states.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The identity never spells a library. This makes that true end to end:

- uidx.json's `headless` may be an object: `manifest`, a `profile` (how the
  library reflects props, its own states and parts — attributes, data
  attributes, classes, `::part()` or `data-part`) and `bindings` (its tag,
  part, event and attribute names per component). The code target and
  conformance read both, so the checkbox renders as `sl-checkbox`,
  `sl-checkbox[checked]::part(control)` and `sl-change` from the same file
  that renders over @hwc/components. Defaults are the example's conventions.
- When nothing is named, the server discovers libraries the project's
  dependencies ship (the `customElements` field of package.json) and the
  Contract tab offers them, or any path; the choice is written into
  uidx.json through the headless route. The tab says which tag a binding
  renders a component as.
- `uidx codegen` passes the profile and bindings through from uidx.json.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…b that edits the contract

Phase 3 — a state is designed the Figma way. Derived variants are
addressable, so selecting the hover checkbox on the canvas shows its root
frame in the inspector with a "state=hover of Checkbox" chip, and a change
to it becomes a cell of the `state="hover"` row through the new `style`
patch op (format), rather than a patch to a node with no source. An edit
to the default combination lands on the base tree; a position, a name or
structure is refused with the reason. Undo inverts the cell. The canvas'
own scene-write path resolves against the derived document too. The
toolbar gains a Repeat tool that wraps the selected instance in a
`<Repeat>` on the first open repeating slot; the patcher leaves a Repeat
unnamed, as it does a Variant.

Phase 4 — the Contract tab edits the contract. The new `contract` patch op
writes one `<Prop>`, `<Event>`, `<Slot>`, `<State>` or `<Part>` in
canonical form, creating its list and the `## Contract` region when absent
and removing a list its last item leaves; it inverts to the previous
declaration. In the tab, each declaration opens into a form (description,
type, default, sample, visual and controllable; a slot's repeats, `of` and
`accepts`), a row adds one, and "Fill from library" declares what the
implemented element exposes and the contract lacks, with the manifest's
descriptions or placeholders the tab marks. The viewer's library model
carries the manifest's types and descriptions for that.

Docs: ADR 0013 §3, ADR 0016 §4, ADR 0017 §2, a design-system walkthrough
in the README, the example README, the changelog.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ate button

uidx.json gains an optional `codegen` — `out` (relative to it) and
`targets` — that `uidx codegen` reads when `--out` is absent and that the
server's /__uidx/codegen route runs for the Contract tab's Generate
button: the same generator over the same pages with the same library, so
the panel and the command line write the same files. A page that does not
parse or a contract the library contradicts stops the run before anything
is written. The example names ../generated and the button reproduces its
36 committed files byte for byte. Issue #37 and the PR description now
record everything delivered.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
CI typechecks before it builds, and the example's tsconfig did not carry
the base config's `customConditions: ["development"]`, so `@uidx/format`
and friends resolved to a dist that did not exist yet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…sories

Advisories published after main's last green run; every fix is a patch
release inside the existing ranges. The release audit passes again.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`repeat="{items}"` on any layer of a component draws it once per item of a
list — a list prop of the contract, or a list field of an enclosing item
(`{person.tags}`), which nested makes a tree. `as` names the item, `count`
the canvas's rows (the model's samples decide otherwise). A repeat on a
`<Slot>` is the consumer-fillable one: a render prop named after the slot in
React, with the placeholder as the default. `<Repeat>` and the contract
slot's `repeats`/`of` are gone; `accepts` stays.

The canvas draws the first row as the layer itself and the rows after it as
generated echoes; a repeat edit rebuilds the scene. The scene and the
Contract tab resolve a list's model across pages, as `uidx check` does, and
the audit tells a shared model from no list at all. A base layer of a
derived component selects its default-state twin on the canvas, and the
default state on the canvas is the base layer in the inspector.

The viewer's Contract tab shows Over / As / Count on any part, slot or
instance; the Repeat tool writes the attribute in place. Example, ADRs
0013/0017, changelog and READMEs follow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Guy Behar and others added 11 commits September 30, 2026 17:16
A Models face beside Tokens and Fonts gathers every model of the document
with the page that declares it and the components that receive it, and
edits it in place — description, and a table of fields with type, key,
optional, sample and words. A new model goes on the page the author picks,
a page called `models` offered as the shared one; models a contract names
and nobody declares are listed and declared in one click.

Two patch ops carry the writes: `model` and `field` reprint one `<Model>`
canonically, create the region before `## Examples` or at the end, remove it
with the last model, and invert — a removed model comes back with its
fields.

The model index the canvas draws from is rebuilt only when some page's
models change, and the canvas redraws its repeats when it does, so a sample
changed on the face redraws every row that shows it, wherever the model
lives. A repeat's row in the Contract tab links to its model on the face.

Closes #38.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`count` only overrode how many sample rows the canvas drew; code never
read it. The repeat API is now `repeat="{list}"` and, when the item needs a
name, `as`. The canvas draws one row per sample of the model — its longest
sample list, three when it has none — so the rows follow the model and
there is nothing to keep in step with it.

Parser, scene, HTML target, Contract tab, example, tests and docs follow.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An instance inside a repeat shows what each contract prop of its definition
receives (ADR 0017 §2): the enclosing item whose model matches the prop's
type, nearest first, else the innermost item for the first model prop —
the rule the code target follows — and the use may say otherwise with
`props={{ node: '{child}' }}`, an item or a field of one, which the code
target now passes as written.

From the walkthrough that built a Tree over a TreeNode model: a Bind picker
beside a text's content offers the enclosing items' fields, a model prop's
fields and the component's props, and a `{…}` typed there is written as the
binding it is rather than resolved away; a slot the tree has and the
contract lacks is listed with Declare; a repeat on a container holding one
row offers to move onto the row; Make component warns when the page already
has a component; a new slot hugs; placing an instance with a slot selected
fills it; the picker leaves out the enclosing component; an emptied
`## Contract` heading goes; `uidx codegen` takes directories.

Fixed: the incremental patch path rebuilt a component without its spec after
a structural edit, so the Contract tab lost the lists until a restart; and
the symbol checker reported `repeat="{items}"` and `{item.field}` as unknown
component properties, in the shipped example too. A CLI test now checks the
example.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A visual prop draws its state set as soon as it is declared, rows or no
rows, so the first style row is written by selecting the state on the
canvas rather than by hand (ADR 0016 §4). Rotation is a look a state may
set — a chevron turns when a row opens — and the CSS target already renders
it as a transform. A use's bound value chooses the state on the canvas
(`props={{ open: '{item.expanded}' }}` inside a repeat), resolved in the
consumer's scope and mapped onto the state axis, so rows repeat open or
closed as their samples say; the code target passed this already.

The canvas now receives the shared model index it was meant to — the
binding had landed on the layers rail — so a list's model on another page
draws its samples on the canvas, not only in thumbnails. A text bound to a
boolean or a number is spelled out rather than crashing the text layout.
Pen-drawn vectors stay stroked in both code targets. The Repeat tool offers
the enclosing item's lists first and names a nested item after its list
(`child` for `{item.children}`). A sized component's layout reads Free, so
choosing Column writes it. An instance's bound prop is no longer called
mistyped.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A page that cannot be drawn keeps its last frame and says why in a
banner instead of going blank. A number field commits on Enter (by key,
so the numpad's too). Declaring a prop offers its type, and a boolean
comes with default={false}. A checkbox, a number and the visibility pill
bind to an enclosing item's field or a prop from a picker beside the
control, the way a text already did; any {…} committed from the
inspector is written as the binding it is rather than as a value.

The visibility pill's picker previously listed only declared BOOLEAN
props, so a tree's children frame had no way to follow item.expanded
from the inspector.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ebflow, Figma and Penpot

Text is typed where it is drawn: the Text tool opens the new node for
typing as soon as the file echoes it, a double-click on a text reopens
it, and Escape or ⌘Enter keeps the words as one characters write. A
right-click selects what is under the pointer and opens the toolbar's
actions with their shortcuts, disabled where the selection cannot do
them. The status strip says "Saving…" while edits are in flight and
"All changes saved" after. An empty page says how to start. The
"Inside …" breadcrumb names the container by its current name rather
than the id it was entered by.

The Contract tab leads with properties and slots; the code binding and
the code library fold under "optional · for developers". The overview
counts pages "with content" and "problems" instead of "drawable" and
"parse"; the file-error overlay leads with plain words and keeps the
diagnostic beneath; the Models face explains itself without jargon.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A token referenced only from a <Styles> row showed 0 uses, a rename
rewrote only its declaration, and `uidx check` stayed green while codegen
emitted an undefined CSS variable, so a state's look broke silently. The
dependents index, the refactor plans (a `style` patch per cell) and the
unresolved-reference check now read doc.spec.styles beside the tree.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
…d ops

The agent edit surface (uidx apply, MCP uidx_apply, eval) could only
edit the scene tree, and the skills `init` installs taught the model ADR
0016 replaced: props={{…}} and one <Variant> tree per combination. Now:

- ops set_style, declare, set_model and set_field compile to the format's
  style, contract, model and field patches, gated like the rest; eval
  gains style/declare/undeclare/model/field builders;
- uidx-authoring teaches identities (contract, <Styles>, models,
  repeat, behaviour), eval-api shows a contract-first example, and a
  new uidx-design-system skill gives the order of work;
- UIDX153 warns when <Variant> trees share one anatomy and differ only
  in values, pointing at a styles table (ADR 0016 §5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
…code

ContactList's "shown instead of the options" lived only in Behavior prose,
so the canvas drew "No contacts yet" under the rows and the generated
React rendered it unconditionally. `empty` is now a built-in state of a
component with a list prop, joining the state axis once a <Style> row
names it (as hover does). The canvas draws it with no rows for the
component's own lists; CSS keys it on [data-empty], which the React
wrapper sets from its lists. A state row that targets a <Slot> now
selects [data-slot] and keeps display: contents.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
The viewer showed none of a component's prose: the intent, ## Behavior
and ## Examples were parsed and never drawn. The Docs face (beside
Elements, ?view=docs) is the component's documentation page read from
its identity: status and headless element, the intent, examples drawn
by the canvas's own renderer as one-instance pages (a root state or a
prop value per <Set>; counts and per-row sets are listed as not drawn),
properties, events, slots, parts and states, accessibility, form and
composition, behaviour rules with anchors, and every page that uses it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
ADR 0015 §3 says an example is exactly a Storybook story; nothing emitted
one. `"stories"` (with `react`) writes <Component>.stories.tsx beside each
React wrapper: the default story with every prop given its sample, its
default or its model's sample rows; one story per value of each visual
prop; and one per ## Examples scene the args can express (a visual
boolean state, a prop value, a count trimming a repeating slot's list).
Verified to type-check against @storybook/react 9 and React 19.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
claude and others added 30 commits October 1, 2026 03:03
A component can now get an enum prop ("choice": two or more values,
visual, defaulting to the first) and turn on hover, focus, pressed and
empty states from the tab; before, no UI could write the first row that
makes an interaction state exist, so hover could not be designed.

The style op takes an empty target and prop to address a whole row:
written with its cells ({} seeds an empty state row) or removed with all
of them, and inverted either way.

Fixes: a prop's Default or Sample typed as false no longer deletes it
(only visual and controllable are on/off flags), and clearing a
description puts the placeholder back instead of being ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
…s, new files

What a user could not do from the editor before, and now can:

- choose a new token's type (Color, Number, Text, Toggle); a new
  collection could only ever hold numbers
- add, rename and remove a collection's modes, so dark mode can be made
  in the editor; values start from an existing mode and node overrides
  follow a rename (schema setCollectionModes)
- rename a collection, with every reference and override repointed
  (schema renameCollection); a new collection offers its name at once
- set a collection's tier
- edit a token's description and the pickers it is offered in (scopes)
- create a component identity (with a starter contract) or a tokens file
  from the New file dialog, and a tokens file from the empty Tokens tab

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
…e, Go to main component

- An instance offers a picker for a contract prop typed as a union of
  words, and refuses any other value; before it was a free text field.
- A component's status is a draft/stable/deprecated picker in the
  inspector header instead of a read-only chip.
- The canvas menu gains Duplicate (also Cmd/Ctrl+D; the copy goes after
  the original under a free name, beside it when the canvas places it),
  Rename (the layers rail's inline rename) and Go to main component
  (opens the page that declares an instance's component and selects it).
  The component an identity file describes is not duplicated, since a
  copy would have no contract.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
The prose half of an identity had no write path: Behavior, Examples and
the intent could only be edited in the file. Two format ops add one:

- region {name: Behavior | Examples, body?} writes a region whole, in
  canonical order when it is new, and removes it without a body;
- intent {text} rewrites the prose above the visual contract.

Both are checked by the parser like any write and invert exactly.

The Docs face edits them as drafts saved as one op (one undo step):
rules as id + sentence rows, examples as a name plus property rows with
a picker for choice props, a toggle for booleans and the state axis
offered; mistakes are named in the editor before anything is sent. Empty
sections now invite their first entry instead of hiding.

Fix: behaviour rule text kept its backticks only in the file; the parser
dropped them, so the Docs page never showed code and a re-save would have
lost them. It now reads the rule from the source.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
<Accessibility>, <Form> and <Composes> were shown in the Docs face and
nowhere editable. A contract-element op writes, replaces or removes each
one in ## Contract, in ADR 0013's order (so undo puts it back where it
was), creating the region when needed; it inverts exactly.

The Contract tab gains an "Accessibility and forms" section: role (with
the common ARIA roles offered), keyboard, accessible name, whether the
component takes part in forms and what it submits, and the components it
is built from. Emptying every field of an element removes it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
Pages could only be created. The server's pages route now also renames a
page (the file moves beside where it was, its frontmatter id follows;
nothing refers to a page by file, so no other file changes) and deletes
one (never the last). The rail offers both on hover: rename inline,
delete after a confirmation that counts the uses of the page's
components on other pages, which a delete would break.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
A prop could not be renamed; deleting and re-adding lost its place and
broke every reader. The contract op takes a rename (the declaration is
rewritten under the new name in place, and inverts), and a rename plan
carries the {prop} bindings in the component, the style rows keyed by it,
the examples that set it and the props of every instance on every page,
one batch per file.

The Design face no longer offers the legacy props={{}} editor for a
component with a contract: it points to the Contract tab, so there is
one place to declare a property.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
Three faults made multi-file edits hard to take back:

- Each file of one action (a collection, token or prop rename, a delete
  that inlines values elsewhere) was its own undo entry. Batches now
  carry a group and join one entry, so Cmd/Ctrl+Z undoes the action.
- A write to a page other than the open one came back from the server
  looking like somebody else's revision and was recorded twice. The
  viewer now remembers every batch it recorded until it is confirmed.
- The inverse of a rename (set name) named the old address, which no
  longer resolves once the rename lands, so undoing any rename failed.
  It now names the node where the rename put it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
- Detach instance (menu, Cmd/Ctrl+Alt+B) writes the layers an instance
  draws into the page as a plain frame: the variant its props select,
  {prop} bindings replaced by the values it showed, overrides applied,
  slots filled with what it put in them, token bindings kept.
- Deleting a component that instances still use now says how many and
  where, and what to do instead, rather than failing on the server.
- Canvas: a node that keeps its address but changes element (an instance
  detached into a frame) now rebuilds; the incremental path kept the old
  instance's generated subtree on screen until a reload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
Only SVG could be dropped; a photo or bitmap logo was ignored without a
word. The server now takes an upload into the document's first assets
folder (declared the moment it lands, identical bytes reused, names kept
unique), and the canvas places it as a rectangle with an image fill at
its own size, capped at 480px. Anything else dropped is named in a
notice instead of ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
A slot could only be filled by dragging an existing layer onto its hole
on the canvas. The instance panel now lists the component's slots: an
empty one offers text or the components its contract accepts, a filled
one names its content (click to select it) and can be emptied back to
the component's default.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
…readers

- The Contract tab lists the styles table by row (base, hover,
  tone=danger + focus…) with how many changes each makes; a row opens to
  its cells, and a cell or a whole row can be removed.
- A model can be renamed from the Models face: the model op takes a
  rename (fields and place kept, inverts), and the plan retypes every
  prop and field that names it on every page.
- Renaming a field keeps its place (the field op takes a rename) and
  carries the {item.field} / {prop.field} bindings inside the components
  that receive the model; it used to move the field to the end and leave
  those bindings pointing at nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
…theme

- One top bar on every view: the logo and document › page breadcrumb,
  the faces (Design, Docs, Tokens, Fonts, Models) as icon tabs, undo and
  redo, save and connection state, a light/dark switch, Ask AI when the
  assistant is reachable, and a primary Get code button (explains how to
  configure codegen.out when it is not set). The old stacked nav and the
  status footer in the left rail are gone.
- The left panel on a page has Layers and Insert tabs. Insert gathers
  the drawing tools, an image from disk, ready-made blocks (Stack, Row,
  Card, Button, Input, Heading) that insert into the selected frame or
  beside existing content, and every component, searchable.
- A light theme: the tokens in theme.css redefined, chosen in the bar and
  remembered per browser, defaulting to the system preference. Rulers,
  page thumbnails and doc examples follow it.
- The overview page is centred and roomier: larger title, cards with
  rounded corners and shadows, section headings with counts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
- Dark is the default again; light stays one click away in the top bar.
- Left panel: the pages list is always on top on every face, so moving
  between files never hides behind a tab; Layers and Assets (blocks,
  images, components) sit below it. The page name in the top bar opens
  a menu of every file, with New file….
- Right panel: Design | Contract | Code. Code shows the selected
  component (or the one an instance uses, or the one a layer sits in) as
  the code it becomes — React, HTML, CSS, the contract JSON — rendered by
  a new GET /__uidx/code route in memory and written nowhere, refreshing
  as the design changes, with Copy and Write to <codegen.out>.
- Bottom bar: a + button opens the Insert panel floating above it; it
  closes on Escape or after inserting. Menus and popovers in the top bar
  close on Escape or a click outside.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
The link from identity to code had a picker for the library and the
implements tag, and nothing for the rest of what uidx.json holds. The
right panel gains a Connect tab (Design | Contract | Connect | Code):

- a status card: Button → <acme-button>, the React component it renders
  onto, and how many props, events, slots and bound parts it has;
- Headless element (saved in the component's .uidx file): the web
  component or headless element it implements, from the library;
- Names in the library (uidx.json headless.bindings): tag, attribute and
  event names only where the library differs, suggested from the
  library's own attributes and events;
- Existing React component (uidx.json codegen.react): module, export,
  prop and callback renames, omitted props, what becomes children;
- Project (uidx.json): the library (from dependencies or a path), how it
  spells boolean props, custom states and parts, the output folder and
  the code targets.

Each field says which file it is saved in. A new /__uidx/config route
edits only these fields, re-validates the whole manifest and restores
the file when a change would make it invalid. The Code tab re-renders
whenever the connection changes, so mapping a prop shows up in the code
straight away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
examples/shoelace: four identities (Button, Input, Checkbox, Switch) that
implement Shoelace's sl-* elements and style them through ::part(), with
light/dark tokens, connected in uidx.json through the Connect tab. A demo
page mounts the generated HTML beside the generated React components, logs
their events, and flips the token mode with the generated Switch. Its test
audits every page, checks conformance against Shoelace's manifest, and
asserts generated/ is current.

What a general-purpose library surfaced, fixed with tests:
- adopt: a part and a slot sharing a name (sl-input prefix) no longer clash
- profile coverage 'subset' checks only what the contract declares; native
  DOM events count as dispatched
- shadow parts pass slot content through with slot="name"; empty library
  slots print nothing; parts resolve to the root's own cssParts first
- text an element renders from an attribute is written as that attribute
  (HTML) and typed string (React)
- React booleans reach custom elements as true/false (Lit keeps a reflected
  attribute set to undefined)
- focus on a shadow host is :focus-within, which it matches
- an outlined vector's stroke is its colour, not a border
- tokens.css carries every mode as [data-<collection>="<mode>"] blocks

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
Built a Shoelace Switch from an empty component using only the canvas and
panels (connect, fill from library, draw, nest, bind parts, design each
state on its variant, Get code), then ran the result on real Shoelace.
Each fix carries a test.

- format: an attribute edit or a re-lowered node now moves the spans of
  the regions after the tree, so a contract op right after connecting an
  element no longer splices into the middle of a tag (Fill from library
  was rejected with UIDX003)
- viewer: a click on a panel number now puts the caret in its edit box;
  typed digits used to reach the layer tree and rename the layer
- viewer: Design-tab edits on a component with states land on the
  default-state twin, for its layers and for the component itself; they
  were silently dropped when the layer was chosen in the Layers panel
- viewer: a token picked for a state variant writes that state's style row
  instead of doing nothing
- codegen: an axis that hugs is not given the width or height measured
  when it was drawn; per-side stroke weights win over the uniform one

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
Fixes everything the designer-style Switch build ran into, then rebuilt
the Switch from scratch with the new gestures and ran it on Shoelace.

- Frame selection (⌘/ctrl+alt+G, menu, toolbar): wraps sibling layers in a
  hugging auto-layout frame; shift/ctrl-click gathers rows in the Layers
  panel
- Drawing or dragging onto a component with states nests into the frame
  under the pointer (the default-state twin stands for its base layer)
- A layer drawn or moved into an auto layout carries no x/y
- Fill from library opens a checklist: identity names (helpText, change),
  the usual members ticked, the default slot included, and the library's
  spellings saved as bindings in uidx.json
- Bind by name binds every unbound part to the layer named after it; the
  Connect tab counts parts as the Contract tab does
- The Hug/Fixed control shows what the canvas draws when no mode is written
- Letting go of a look on a state clears its cells (weights with strokes)
  and keeps the state's row; stroke weights on a state write one weight
- Token pickers: list keys (arrows, Enter picks the first match), the
  colour picker's Libraries search takes focus, and collections that fit
  the property come first (type for a font size, not space)
- Shift+Enter / Enter select the parent / children, variants included
- The canvas lays the page out again once the bundled fonts load, so a
  component's variants no longer collapse with clipped labels

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
Design a List whose repeated item slot accepts any item component, and
inject PersonRow, PersonCard and PersonChip into List instances without
touching List itself. Fix the gaps the flow exposed:

- Contract: model types (Person, Person[]) in "Type to add" and the
  prop type input; undeclared slot message offers "Declare slot".
- Properties: text pills offer item fields ({item.name}); injected
  content gets the layout controls of the slot it fills.
- Canvas: repeated slot fills render per-item data for every row, with
  only the first copy editable; rebinding a prop to/from the contract
  rebuilds the scene instead of leaving it stale; cross-page models
  resolve for instance defaults.
- Codegen: wrapperless repeated slots emit row rules on the real
  children instead of a dead [data-slot] rule; ellipses get a round
  border-radius.
- Insert panel focuses its search field.

Shoelace example gains the list, item components, a Team page and a
"List with injected items" demo section.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
Rework the instance inspector around what a designer does with a list:

- Component card: thumbnail, description and a "go to component" button.
- Data group: where each contract prop comes from here - the list's
  sample rows, "each item of list-2" for injected content, a receives
  picker inside components, or a preview stepper for a lone item.
- Slot cards: say when a slot repeats, show what fills it (Default tag
  for the component's own content) and open a content picker with real
  component thumbnails, components that receive the row's item first,
  then other components, Text, Nothing and Use default. A swap is one
  undoable edit; a choice that cannot receive the item is called out.
- Injected content says which instance's slot it fills.
- A selected fill slot offers the same card instead of two buttons.

Fix what the earlier validation left open:

- Empty slots are outlined on the canvas overlay (not in the scene, so
  layout and export are unchanged) and clicking one selects it. ADR 0007
  amended.
- Item components preview any sample row ("Preview data" stepper), with
  the canvas redrawing; editor state only, nothing written.
- "failed to open <0> as a font": the canvas asked for Inter before the
  host font loader was installed, and the SDK cached a 404 body as the
  face. The loader is now installed before the first await.
- Contract descriptions keep code spans and spacing ("`item`;" read
  back as "item ;"), which also fixes the design-system example's docs.
- Real descriptions for the list example's components.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
Selecting a slot inside a component now shows one Slot panel, on the
Design tab and the Contract tab alike, in the order a designer decides:

- Description, or "Declare slot" when the contract lacks it.
- Content: "Filled once" or "For each item". For each item picks the
  list prop (shown with its type, e.g. items · Person[]), or creates a
  new list property of a chosen model and repeats over it in one edit.
  Item name, and the model as a chip that opens it, with the canvas's
  sample row count.
- What each filling receives (item: Person, index: number) and the
  React prop a use fills it through (renderItem?: (item, index) => ...).
- Default content chosen from the same picker uses get, with a check
  that the default receives the item or a warning that it cannot.
- Allowed content: "Any component" or the headless element fillings
  must implement, listing which components fit and flagging a default
  that does not.

Fix: the instance picker treated `accepts` as component names, while
ADR 0017 and the audit read it as the element a filling implements, so
a slot with `accepts` offered nothing. It now matches `implements`.
Also fix stray spaces in the Contract tab's repeat hint.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
Repeat is per layer (ADR 0017 §2): a frame, text, instance or slot inside
a component can draw itself once per item, and everything inside it is
the template. The Design tab now has a Repeat section for every such
layer, and the Slot panel and Contract tab use the same section:

- Once / For each item. The list picker groups the item's own lists
  first ("From the item it is inside": item.members · Person[]) for
  nested repeats, then the component's list props; "+ New list
  property…" declares a list of a chosen model and repeats over it.
- A first repeat names its item so it hides nothing: `tag` inside a
  row of `item`s, `person` in a component whose prop is already `item`.
  The toolbar's Repeat tool uses the same rule, now also avoiding props.
- Item name and model (opens the model), with the canvas's row count.
- "Part of group, drawn for each item (Group) of groups": the repeats a
  layer already belongs to, each selectable.
- The template: the item's fields a layer inside can bind to, list
  fields marked as nestable, and what already reads the item — bound
  texts, nested components that receive it, nested repeats — each
  selectable. "Repeat the row instead" when a repeat wraps one row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
Repeat comes down to the two choices a designer makes, and nothing is
passed or guessed behind their back:

- Models face: each model has Items (one row per item, one column per
  field; add, edit, remove) and Fields (the shape). Items are stored as
  each field's sample list, so files, canvas and code are unchanged.
- Repeat section on any layer inside a component: a switch, then a
  model picked from a token-style list ("Person · 5 items"). The list
  prop is chosen silently: a list of that model the layer can reach, or
  a new `people: Person[]` declared in the same edit; the item is named
  so it hides no prop or outer item. "Edit items" opens the model. The
  slot panel uses the same section; its receives box, render-prop line,
  accepts control and inference checks are gone (accepts stays on the
  Contract tab).
- Binding: inside a repeat, a component's properties and a text's
  content bind to the item or its fields from a bind button, like
  tokens ("From the item": item, item.name...). Bound values show as
  pills with unbind.
- Explicit only: a nested component gets the row's item only when a
  prop is bound to it (`props={{ item: '{item}' }}`). Removed the type
  inference from the canvas, codegen and the Contract tab; the canvas
  draws an unbound item component with the preview sample. The examples
  bind explicitly and generate byte-identical code. ADR 0017 amended.
- `uidx check` accepts a contract prop passed through `props={{…}}`
  (it only knew the older props declarations).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
- Labels: the panel says "This Person" and "This Person › name" for a
  repeat's row and its fields, in the bind popups, the text binding
  popup ("From each row") and on bound pills. The file keeps `{item}`
  and `{item.name}`; the row's code name stays out of the designer's way.
  A component's own prop reads by its name ("person › name").
- Bind on placement, explicitly: a component that takes the row's model
  is bound to it when it is placed — from the Insert panel into a
  repeated row, from a repeated slot's picker (uses and defaults), or
  when a layer holding it starts repeating. Written into the file as
  `props={{ person: '{item}' }}` and shown as a removable pill; nothing
  is inferred afterwards.
- Example components take `person: Person` instead of `item`, so a use
  reads `person ← This Person`; generated React follows
  (`<PersonRow person={item} />`, `PersonChip({ person })`).
- Test: a component's text property bound to `{item.name}` draws each
  row's own value.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
A coach points at the real control to use next and moves on when the
document shows the step done, however the designer did it. Tutorials are
offered on the Overview (with a first-visit welcome) and from a Learn
button in the top bar; a run survives a reload.

Fixes found while walking the tours in the editor:
- a component gaining its first state redraws live instead of blank
- a repeated text is offered its own item's fields to bind
- Data rows no longer repeat props Properties already edits
- the first field of a model keeps the card on Fields
- the coach card steps aside from popups it would cover

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7AN4RSMLMWKACuggBMYEH
Instances:
- An instance that states a width or height is Fixed on that axis; one
  that states neither follows its component (hug, fixed or fill). An
  authored sizing mode, stretch or grow on the instance still wins on its
  own axis. Before, the size was written to the file but the component's
  hug redrew it at its content size.
- A component that wraps one frame (a styles table, or a bare Component
  around a Frame) hands the instance's size to that frame, so the visible
  pill resizes with the instance, not just its invisible wrapper.
- Same-file edits land where a rebuild would: the update path recomputes
  the wrapped frame, and a sizing flip or a parent layout change that
  moves which axis a stretch fills rebuilds.
- The canvas drag no longer snaps back. Side handles write only the
  dimension they move; resizing a stretched or growing instance makes
  that axis Fixed, as Figma does. Width and Height offer Fixed/Hug for an
  instance; Hug removes the stated size. The file only ever gets
  x/y/width/height, never sizing modes on an Instance.

Tutorials:
- A step done while a popup it opened is still open (the color picker)
  ticks and says to close it, and moves on only once it closes.
- The ring and the scroll follow what actually shows: the target is cut
  by the window, every scrolling panel and any sticky header over it, so
  Appearance is scrolled out from under the inspector header instead of
  ringed behind it. The coach retries while the layout moves it, stops
  once the designer scrolls by hand, and scrolls again after Back. The
  ring hides while a popup covers its target.
- No step passes on an editor that has not loaded its pages yet, so a
  reload mid-tour no longer skips steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The canvas preview only moved the instance's own node. A component with a
styles table draws inside a variant wrapper, on a frame that carries the
instance's size, and only a build or the file's echo put the size there. So
a drag moved the selection box on every pointer move while the visible pill
stayed at its old size, sliding with the wrapper, and jumped on pointer-up.

- The frame half of reconcile's update-instance-root is now
  wrappedFrameUpdate, the one rule for what the wrapped frame takes from
  its instance; instanceDefinition finds the definition the build expands.
  The echo and the preview both ask them, so they cannot disagree.
- The viewer draws that frame for every preview of an instance's size: a
  drag on any handle, a width or height typed into the panel, a stretch or
  grow let go of. A pointer cancel puts it back, and a document landing
  mid-drag is applied to the scene the file drew, then the preview again.
- The file is unchanged: still only x/y/width/height on the Instance.
- A journey drags a Button instance's handle and checks the drawn pill
  before pointer-up; it fails with the preview reverted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A placed component is a black box with a styleable outer box, the way an
HTML element is styled from outside. An <Instance> may state its outer
box's fills, strokes and their weights, alignment and dashes, corner
radii, opacity, effects and padding, plus a textFills colour that every
text inside takes. The component's own layout, gap, alignment and child
layers stay locked. The use sits above the component's resting look and
beneath its state rows, so a restyled button still shows its hover.

- Schema: a role table (instance-box.ts) says what an instance may state.
  State rows stamp what they write, so the use layers beneath them. The
  box lands on the node that draws the look (a wrapped frame, a
  composition's one instance, on through bare wrappers), the same node a
  stated size pins. The text colour reaches the nearest use's texts; slot
  text that states its own fills keeps them.
- Incremental updates land where a rebuild would, canvas edits never echo
  box attributes back into the file, and detach carries the box onto the
  frame that drew it.
- Editor: for an instance the Design panel shows the component's values
  dimmed, an override dot and reset per row and section, Reset all in one
  undo step, a Text color row, padding unlocked and the inner layout as a
  read-only line. Previews draw live through the wrapped-frame path.
- Code: resting rules read --uidx-* hooks with the component's values as
  fallbacks, state rules stay literal, and every root resets the hooks.
  React hands a held instance its hooks and a stated size through style.
  Contracts list the box. Example goldens regenerated.
- Checks: uidx check warns on a locked attribute (UIDX154) and a box value
  that does nothing (UIDX155); the agent's edit tools follow the same
  roles.
- ADR 0018 (amending 0012), an ADR index, and the property, panel and
  authoring docs.
- A scene snapshot of every example page guards that only restyled
  instances change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The three code tabs each opened differently, padded their content twice,
and printed raw server errors in full: a missing library file showed an
ENOENT message with an absolute path that ran past the pane, red at the
top of Connect and amber at the bottom of Contract. They now share the
Design tab's grammar and say each problem once, in one place.

- Shell: the tab strip always fits, with a small dot only on a tab that
  has a real problem (unbound parts are progress, not a problem). The
  identity row stays on one line; read-only is a lock chip there. One
  subject resolver decides what the three tabs are about, so an inner
  layer or an instance reads the same on Connect and Code.
- Shared parts: InspectorSection (the Design tab's section head, with an
  (i) for the teaching copy), InspectorStatus (one status line: a short
  title, a count, details on demand, the raw error behind Details) and
  InspectorEmpty. inspector-messages.ts words every fault and empty state
  once, so the tabs cannot disagree.
- Ownership: Contract declares and binds; Connect wires the library,
  names, React component and output folder; Code previews the output and
  owns Write code. Contract's library footer, chooser and Generate button
  are gone, with links to where they now live.
- Code: the file switcher never wraps, Copy and Write are disabled when
  there is nothing to copy or write, diagnostics read as "Unknown part
  “spinner-track”" with the file and line, and a wrap toggle.
- Library and config: uidx.json still loads when the library cannot, and
  the server adds a code and the configured path to its library error
  (additive; the message is unchanged).
- Docs: ADR 0013 and 0017, inspector-design, README and CHANGELOG follow
  the new ownership.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DTPs41geuetWYjWSKmsBRu
An adversarial review of the shared frame for Contract, Connect and Code
(each finding reproduced by a second reader before it was fixed) turned
up these, among smaller copy and layout nits:

- An (i) on a collapsed section now shows its tip; it used to press and
  show nothing, hiding the teaching copy on Names, React and Project.
- A refused library choice no longer marks the working library as
  failed on every tab; the error stays under the path field, which
  stays open with what was typed.
- Long component names no longer push section actions, chips or the
  footnote past the pane, and two tab dots no longer cut every tab
  label at the narrowest inspector.
- Open contract, Bind parts and Show select the component before
  opening its Code binding. The Code dot follows the code whether or
  not the tab is open, and a stale "Wrote N files" no longer sits over
  a render that broke.
- Status rows wrap their diagnostic instead of cutting it, and the
  title keeps priority over the count.
- Contract groups its declarations by kind, the slot view and an
  undeclared slot use the shared row and wording, and a slot fill
  inside an instance names the slot it fills.
- Hints use one grey that passes contrast in the light theme, the
  Write code footer stays on the pane's bottom edge, and switching tabs
  starts at the top.
- properties-pane.test.ts unmounts every pane after each test; leaked
  panes re-rendered on each shared-state write and timed tests out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DTPs41geuetWYjWSKmsBRu

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants