Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
4893949
docs: ADRs 0012–0017 — the design-system model
Sep 29, 2026
6a0782e
format: spec regions, styles table and <Repeat> (ADRs 0013–0017)
Sep 29, 2026
db87621
schema: derived variants, model bindings, <Repeat> expansion, design-…
Sep 29, 2026
1f82b5a
codegen: HTML/CSS and React rendered from identities; example design …
Sep 29, 2026
e1d0092
design-system: draw derived sets faithfully, prop samples, pass-throu…
Sep 29, 2026
10d0272
viewer: Contract tab binds components, parts and repeats to the headl…
Sep 30, 2026
2059fad
codegen, viewer: shadow parts (cssParts) beside element parts
Sep 30, 2026
749a575
codegen: a shadow part is not a finding
Sep 30, 2026
419fe7e
contract: booleans are states, models are shared, parts are described
Sep 30, 2026
7eb5c7a
lint: drop imports the contract move left unused
Sep 30, 2026
f6bea76
lint: the React emitter no longer imports ModelSpec
Sep 30, 2026
c5c28ab
headless: library profile, per-library bindings, and discovery
Sep 30, 2026
43ff858
viewer: design states on the canvas, a Repeat tool, and a Contract ta…
Sep 30, 2026
c2d9558
codegen from the viewer: uidx.json "codegen", a server route, a Gener…
Sep 30, 2026
49b96b1
example: resolve workspace packages to source when typechecking
Sep 30, 2026
0ccdb1a
deps: patch-level bumps for undici, brace-expansion and fast-uri advi…
Sep 30, 2026
0289c65
Repeat as an attribute on any layer (ADR 0017 §2)
Sep 30, 2026
223fc9e
Models face: declare and edit models in the viewer (ADR 0015 §1)
Sep 30, 2026
768c2d6
Drop the repeat count: the model's samples decide (ADR 0017 §2)
Sep 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ dist
node_modules
pnpm-lock.yaml
packages/viewer/public
examples/*/generated
examples/*/vendor

.uidx
.uidx-agent
Expand Down
69 changes: 69 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,75 @@ User-visible changes are recorded here. Versions follow semantic versioning;
pre-1.0 releases may change the design format or public APIs. Release notes must
describe migrations when an existing document or integration is affected.

## Unreleased

- The design-system model (ADRs 0012–0017). A `.uidx` file may now carry a
`## Contract` (props, events, slots, form, accessibility; `<State>` only for
states the element produces itself, since a visual boolean prop is a state
and `hover`/`focus`/`active` are the browser's; `<Part>` descriptions are
optional, the tree's `part=` bindings being the declaration),
`## Behavior` guidelines, `## Models` and `## Examples` after its visual
contract, and a `<Styles>` table beside the root. Models are shared across
pages and named by a prop's type (`Contact`, `Contact[]`). Any layer of a
component repeats over a list with `repeat="{items}"` — or `{person.tags}`
inside an outer repeat, which makes a tree — naming its item with `as`; the
model's samples decide how many rows the canvas draws; a repeat on a
`<Slot>` is the one consumers fill. Declarations only: nothing
in the file computes. The viewer draws a styles table as the variant set it
derives, resolves `{item.field}` bindings to a model's samples and `{prop}`
to the prop's `sample` or default, expands a repeat into sample rows (a
model declared on another page included), and resolves an instance's
`props={{ label: '{label}' }}` in the consuming component's scope. A `<Slot>` that states no size hugs its placeholder. `uidx check` audits the regions against each
other and the tree; `uidx contract` prints them as JSON.
- `@uidx/codegen` and `uidx codegen`: HTML/CSS and React rendered from the
identities over headless custom elements, with props, events and slots from
the contract — a plain slot as a `ReactNode` prop, a repeating slot as its
list prop plus a render prop named after it, a repeat elsewhere as a `map`
in place — and each contract checked against the headless library's
`custom-elements.json`.
- `examples/design-system`: Checkbox, Field, CheckboxField, Button and a
contact list rendered end to end over `@hwc/components`, with the generated
output committed and checked for drift.
- A state is designed on the canvas: the derived variants a styles table draws
are selectable, and a change to one writes the matching cell of its style
row (the new `style` patch op), while a change to the default combination
edits the base tree. The inspector names the state it is editing. A base
layer chosen in the rail is drawn by the default state, so that is what the
canvas highlights, and the default state chosen on the canvas is the base
layer in the inspector. The toolbar gains a Repeat tool that repeats the
selected layer over the first list its component's contract can place; the
first row of a repeat is the layer itself, the rows after it its echoes.
- A **Models** face beside Tokens and Fonts: every model of the document
with the page that declares it and the components that receive it, edited
in place (description; fields with type, key, optional, sample and words),
a new model declared on a chosen page — a `models` page being the shared
one — and models a contract names but nobody declares offered for
declaring. The new `model` and `field` patch ops write one `<Model>` back
canonically and invert. A sample changed there redraws every repeat of it,
and a repeat's row in the Contract tab links to its model.
- The Contract tab edits the contract: each prop, event, slot, state and
part opens into a small form (description, type, default, sample, flags;
a slot's `accepts`), a row adds one, and "Fill from library"
declares what the implemented element exposes and the contract lacks. The
new `contract` patch op writes one declaration in canonical form.
- `uidx.json` gains an optional `codegen` (`out`, `targets`): `uidx codegen`
needs no `--out`, and the Contract tab's Generate button renders the code
targets from the server into that folder, reporting what it wrote.
- The inspector gains a **Contract** tab beside Design. A component chooses
the headless element it implements from the library; its parts are bound to
layers from the component's list or from the layer's own row, and any layer
picks the list it repeats over and its item's name. The tab shows the
file's contract beside the bindings and counts the parts still to bind.
`uidx.json` gains an optional `"headless"` path to the library's
`custom-elements.json`, served to the viewer and used by `uidx codegen` when
`--manifest` is absent. Parts a library exposes as shadow parts (`cssParts`)
are offered beside element parts and marked; the code target styles them
through `::part()`. `headless` may also be an object with a `profile` (how
the library reflects props, its own states and parts) and `bindings` (its
names per component), so one design renders over libraries that spell
things differently; when nothing is named, the Contract tab offers the
libraries the project's dependencies ship and writes the choice.

## 0.1.6 — 2026-09-25

- The first `uidx dev` sets up the workspace itself when the install script did
Expand Down
104 changes: 103 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,44 @@ The separate `.uidx/uidx.json` manifest controls which files belong to the docum
{
"id": "my-project",
"files": ["**/*.uidx"],
"assets": ["assets/**"]
"assets": ["assets/**"],
"headless": "../vendor/hwc/custom-elements.json"
}
```

`headless` is optional. It names the headless library's `custom-elements.json`,
relative to `uidx.json`; with it, the viewer's Contract tab offers the library's
elements and parts as choices, and `uidx codegen` checks contracts against it.
Leave it out and the Contract tab offers the libraries your dependencies ship
(any package whose `package.json` has a `customElements` field) and writes your
choice here. The object form binds the same designs to a library that spells
things differently, without editing a design:

```json
{
"headless": {
"manifest": "node_modules/@shoelace-style/shoelace/dist/custom-elements.json",
"profile": { "props": "attribute", "customStates": "state", "parts": "element" },
"bindings": {
"Checkbox": {
"tag": "sl-checkbox",
"parts": { "checked-indicator": "control" },
"events": { "change": "sl-change" }
}
}
}
}
```

`profile` says how the library reflects props (`attribute`, `data-attribute`,
`class`), its own states (`state`, `data-attribute`, `class`) and parts
(`element`, `data-part`). `bindings` maps a component's identity names to the
library's. Both default to the conventions `@hwc/components` follows.

`codegen` is optional too: `{ "out": "../generated", "targets": ["html", "react", "contract"] }`
says where `uidx codegen` writes without `--out`, and gives the viewer's
Contract tab a Generate button that renders the same output from the server.

### CLI and MCP

`npm run uidx` starts the local viewer, file synchronization server, and MCP HTTP
Expand All @@ -119,6 +153,74 @@ npx --no-install uidx fmt --check
The CLI also supports reading, creating, editing, and rendering pages. See the
[CLI guide](packages/cli/README.md) and [agent architecture](packages/agent/README.md).

## Design system: one file, every render

A `.uidx` file can be the identity of a component, and the components you
ship — on the canvas, in Figma, as HTML/CSS, as React — are renders of it
(ADRs 0012–0017). Nothing in the file computes; implementation is a renderer's
job. One checkbox, top to bottom:

```mdx
---
id: checkbox
---

Lets a user toggle one option. The box and its marks are the design system's;
the label comes from a Field.

## Visual Contract

<Page>
<Component name="Checkbox" status="stable" implements="hwc-checkbox"
width={20} height={20} cornerRadius="{radius#sm}" fills="{surface#control}">
<Vector name="check" part="checked-indicator" visible={false} width={12} height={12} … />
</Component>
</Page>

<Styles>
<Style state="checked" root:fills="{surface#accent}" checked-indicator:visible={true} />
<Style state="hover" root:strokes="{border#hover}" />
<Style state="disabled" root:opacity="{opacity#disabled}" />
</Styles>

## Contract

<Props>
<Prop name="checked" type="boolean" default={false} controllable visual>Whether the option is selected.</Prop>
<Prop name="disabled" type="boolean" default={false} visual>Inert and dimmed.</Prop>
</Props>
<Events>
<Event name="change" detail="{ checked: boolean }">Fires once per user toggle, never when set from code.</Event>
</Events>
<Accessibility role="checkbox" keyboard="Space toggles" />

## Behavior

- toggle: click or Space flips `checked`.
- change-event: `change` fires once per user toggle, never when `checked` is set from code.
```

- **Identity.** The visual contract is the anatomy and layout, in Figma's
vocabulary. `implements` binds the component to a headless element,
`part` binds a layer to one of its parts.
- **States.** A visual boolean prop is a state; `hover`, `focus` and `active`
are the browser's and need no declaration. The styles table gives each
state its look, and the canvas draws the whole set. Select a state on the
canvas and change it: the viewer writes the row.
- **Contract.** What the code render exposes, every declaration with its
words. The inspector's Contract tab binds components and parts to the
library named in `uidx.json`, and edits the contract in place.
- **Behaviour.** Short bullets that guide the logic without being code.
- **Models.** For a list, a view model of what each row receives — declared
once, named by a prop's type, sampled for the canvas, never derived. Any
layer repeats over a list with `repeat="{items}"`; nested, that is a tree.
The viewer's Models face edits every model of the document in one place.

Then `uidx check` audits the regions against the tree, and `uidx codegen`
renders HTML/CSS and React over the headless library, checking each contract
against its `custom-elements.json`. See `examples/design-system` for six
components rendered end to end over `@hwc/components`.

## Documentation

- [Drawing icons and custom graphics](docs/graphics-tools.md)
Expand Down
86 changes: 86 additions & 0 deletions docs/decisions/0012-design-system-model.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# ADR 0012 — The design system model: identity, contract, behaviour; components are renders

Status: **accepted**, 2026-09-29. Umbrella for ADRs 0013–0017, which each
decide one region of the file.

## Context

A design system used to be a library of hand-built components that people
assembled. When agents generate most of the UI, generation is cheap and drift
is the default failure, so the question becomes: where does consistency live
when nobody assembles by hand?

UIDX already has the pieces of an answer. A `.uidx` file is a language, not a
picture: primitives (`Frame`, `Text`, `Vector`), auto-layout and constraints,
tokens with modes, named components with props and variants, and addresses an
agent can patch. What it lacks is everything a component *is* beyond its look:
what it accepts, what it does, and what data it shows.

Meanwhile the headless web components in `@hwc/components` hold exactly that
other half — state, behaviour, accessibility, form participation — as custom
elements with no paint, each described by a `SPEC.md` with an anatomy of named
parts.

## Decision

### 1. A component is an identity; targets render it

A component's **identity** is platform-free and theme-free and has three
layers, each in its own region of one `.uidx` file:

| Layer | Region | Reader |
|---|---|---|
| Intent, anatomy, layout, styles | intent prose + `## Visual Contract` | viewer, canvas, Figma export, code emitters |
| Contract: props, events, states, slots, parts, form, accessibility | `## Contract` (ADR 0013) | code emitters, audit, agents |
| Behaviour guidelines | `## Behavior` (ADR 0014) | developers, agents, tests |
| Data shapes and sample scenes | `## Models`, `## Examples` (ADR 0015) | canvas, Figma, code emitters |

A **theme** is token values in modes. A **concrete component** is
`render(identity, props, theme, target)`. Appearance variants are points in
that space and are never authored (ADR 0016); only variants that change
anatomy are authored trees.

The targets are the canvas (today), Figma export, HTML/CSS and React
(ADR 0017). Each target reads the regions it can use and ignores the rest.
The viewer and Figma never read `## Contract`, `## Behavior` or `## Models`
beyond what the canvas needs to draw sample data.

### 2. Declare, never compute

The file declares. It never contains an implementation:

- No TypeScript blocks. The headless element implements behaviour in code and
is proven to conform to the declared contract (ADR 0013 §4).
- No expressions. The only dynamic values are aliases — `{radius#md}`, a
component property `{label}`, or a model field `{item.name}` — and each is a
lookup, not a formula. Derived values arrive as fields (ADR 0015 §2).
- No conditions in the visual contract. Presence and appearance under a state
are rows in the styles table (ADR 0016 §2), not `when` attributes.

### 3. Behaviour lives in the headless layer

A component's `implements` attribute names its headless root, e.g.
`hwc-checkbox`. Its parts (`part="checked-indicator"`) are that root's part
elements. Slots are that root's slots. The design system owns how every part
looks and where it sits; the headless element owns what it does. Composition
(`Composes with`) is the headless layer's graph, which the audit reads.

### 4. What stays out of scope for now

- **Theming beyond modes** — several brands, density, per-product overrides —
is a future task. Modes (ADR 0011 era, story G8) remain the only theming
axis in this iteration.
- Responsive and adaptive layout rules.
- Evals for generated screens.

## Consequences

- One file per component is the single source of truth for what it is. Code,
Figma sets and `SPEC.md` become renders of it.
- The parser gains three optional regions after the visual contract
(ADR 0013 §1). Existing files are unchanged and stay valid.
- `uidx check` gains the rules each ADR names. They are what make the model a
tool rather than a document.
- A new package, `@uidx/codegen`, renders HTML/CSS and React from the file
(ADR 0017), and an example package demonstrates the whole path against
`@hwc/components`.
Loading
Loading