` markup costs SEO.)
There is **no inference of undeclared roles.** Suggesting a role for untagged structure belongs at *creation time* — the optimizer's opt-in `--annotate-roles` pass ([RFC-0041](../rfcs/0041-role-attribute.md)) — never in the score.
**Audit shape** — a plain inventory fact, one per declared role:
```json
{
"role": "tab-bar",
"path": "gui > col[0] > row[3]"
}
```
---
## Output
A passing gate followed by a full CCAC report:
```json
{
"clean": {
"score": 74,
"audits": [
{ "rule": "redundant-wrapper", "severity": "warn", "path": "gui > col[0] > row[2]", "why": "...", "autofixable": true }
]
},
"consistent": {
"score": 88,
"audits": [
{ "check": "token-semantic-misuse", "severity": "error", "token": "$radius-corner-sm", "defined-as": "radius", "used-on": "font-size", "autofixable": false }
]
},
"accessible": {
"score": 91,
"audits": [
{ "criterion": "contrast-ratio", "wcag-ref": "1.4.3", "severity": "warn", "computed": "3.8:1", "required": "4.5:1", "autofixable": false }
]
},
"comprehensible": {
"score": 78,
"audits": [
{ "role": "top-navigation-bar", "path": "gui > col[0] > row[0]" },
{ "role": "card", "path": "gui > col[0] > col[1]" },
{ "role": "tab-bar", "path": "gui > col[0] > row[2]" }
]
}
}
```
A gate failure returns an error — no report:
```json
{
"error": "intact",
"details": [
{ "ref": "$color-brand-primary", "type": "token", "path": "gui > col[0] > text[1]", "reason": "token not declared in
block" }
]
}
```
---
## What This Document Is Not
- **Not a design critic.** It measures the file, not the design. Beauty, composition, hierarchy, taste, and whether a design is *current* are out of scope — they are subjective, plural, and ever-evolving. That judgement belongs to humans and to gui.farm's library of good examples, never to this score.
- **Not a style guide.** It defines measurable file quality, not aesthetic preference.
- **Not a validator.** Conformance to the spec is the gate — `validate.ts` is the authority there.
- **Not a complete accessibility standard.** Only WCAG 2.2 visual criteria are in scope. Interaction and document accessibility are out of scope per P5.
- **Not dependent on gui.farm or any service.** All four levels run locally and offline. gui.farm helps people *create* interfaces; it plays no part in measuring quality.
---
## Versioning
This document tracks the format version. Breaking changes to scoring criteria — new levels, removed audits, changed thresholds — are proposed as RFCs and reflected here.
Current version: `0.2` (CCAC model, RFC-0040). The earlier CCACT model included a fifth level, **Trend**, which scored a design's fashion-alignment against a temporal corpus. Trend was removed (RFC-0040, 2026-06-17): fashion is not a file property, it changes year to year, and optimizing for it homogenizes the ecosystem. Style fingerprinting survives only as a gui.farm discovery facet, never as a score.
---
# @dotgui/kit
**The dotgui engine in one package.** Everything required to take a `.gui` file
through its entire life — parse, validate, render, score — lives here, behind one
version and one install.
```bash
bun add @dotgui/kit
```
---
## What it is
A `.gui` file is markup. On its own it's inert: bytes that mean nothing until
something can read them, check them, draw them, and judge them. `@dotgui/kit` is
that something — the **reference engine** for the format.
It is a **pure, deterministic library**. Same input → same output, every time.
There is **zero AI in this package.** The kit doesn't generate designs, guess
intent, or call a model. It executes the format's rules and nothing else. That
separation is deliberate (see *Where the AI lives* below).
If the format's *meaning* depends on it, it's in the kit. If it's a product built
*on top* of the format (exporters, hosted services, editors), it isn't.
---
## The lifecycle of a `.gui` file
The kit is organized around the life of a single file. Two paths:
### Write path — creating a file
```mermaid
flowchart TD
A(["✍️ Author markup"]) --> V{"Validate · /validate
the only required step"}
V -->|"broken / illegal"| A
V -->|"valid"| W["Write → .gui
zip markup + assets"]
W --> Z(["📦 .gui package"])
V -.->|"optional"| R["Render · /render
see it · or make preview.webp"]
R -.->|"optional preview"| W
Z -.->|"optional · advisory · consumer decides policy"| S["Score · /score
quality report — kit takes no action.
A low score is still a valid file."]
style R stroke-dasharray: 5 5
style S stroke-dasharray: 5 5
```
**Only validate is required.** Everything else is à la carte — a tool that just
needs to produce a `.gui` pulls `validate`, then writes valid markup (+ assets) to
a zip. Render and score are capabilities you reach for *if you need them*, not
gates you must pass through.
1. **Validate** — *is this broken?* The one hard check. Illegal/unparseable markup
can't be saved as a real `.gui` — fix it and retry. The format's equivalent of
a browser rejecting malformed code.
2. **Write / pack** — bundle valid markup (+ assets) into a `.gui`. Minimal output
needs nothing but valid markup. *(This package I/O — read/write/pack/unpack,
asset add/rm, set-preview — is the kit's `package` surface: deterministic file
operations every tool reuses. It currently lives in the CLI and is the next
consolidation target; see ARCHITECTURE.md.)*
3. **Render** *(optional)* — *what does it look like?* The HTML render is the
format's reference output. Also feeds the `preview.webp`; the kit rasterizes
via `puppeteer-core` by default (opt into full `puppeteer` for a bundled
Chromium), falling back to a placeholder preview if no browser is found. See
[rasterize](src/rasterize/README.md).
**Score is not a mandatory step in packing.** Packing a valid file never requires
a score. Score is a separate, read-only quality lens you can run on any valid file
— it returns a CCAC report (Clean · Consistent · Accessible · Comprehensible; all
local and zero-AI; Comprehensible scores how AI-ready the file is as semantics by
reach-coverage over its declared `role=` anchors) and **the kit itself takes no
action on it.**
A low score is still a valid, packable file. Think HTML *best-practice standards*
— advisory, not a parser error.
**What happens with the score is entirely the author's call.** The kit only
produces the number; the consuming tool decides the policy. A CLI might fail a
build below a threshold; the app might show a warning badge; a batch job might
auto-discard known-bad output; another tool might just log it and move on. The kit
hands you a measurement and stays out of the way — it never imposes a gate of its
own.
### Read path — consuming a file
```
.gui bytes → parse → render
───── ──────
/parser /render
```
Parse resolves tokens, styles, and modes into a normalized model; render turns
that model into HTML. That's the whole round trip.
---
## What's inside (subpath exports)
The root exports only the canonical **types**. Behaviour lives in subpaths, so a
consumer only bundles what it imports — import the parser and you don't pull in
the renderer.
Each module has its own README with the full API, examples, and use cases —
linked below.
| Import | What it does | Docs |
|---|---|---|
| `@dotgui/kit` | canonical types for the format | [types](src/schema/README.md) |
| `@dotgui/kit/validate` | `validate(markup)` → schema errors, or clean | [validate](src/schema/README.md) |
| `@dotgui/kit/parser` | `parseXml(markup)` → resolved model (tokens/styles/modes resolved) | [parser](src/parser/README.md) |
| `@dotgui/kit/render` | `render(...)`, `renderToHTML(...)` → HTML (browser-clean) | [render](src/render/README.md) |
| `@dotgui/kit/score` | `score(...)`, `scorePackage(...)` → CCAC report | [score](src/score/README.md) |
| `@dotgui/kit/package` | `unpack`/`pack` + read/edit a `.gui` in memory | [package](src/package/README.md) |
| `@dotgui/kit/lint` | `lintMarkup(...)` → idiom + content findings | [lint](src/lint/README.md) |
| `@dotgui/kit/autofix` | `autofixMarkup(...)` → deterministic repair | [autofix](src/autofix/README.md) |
| `@dotgui/kit/rasterize` | `rasterize`/`renderPreview` → image / `preview.webp` | [rasterize](src/rasterize/README.md) |
### Examples
```ts
import { validate } from '@dotgui/kit/validate'
import { renderToHTML } from '@dotgui/kit/render'
import { score } from '@dotgui/kit/score'
const result = validate(markup) // legal?
const html = renderToHTML(model) // what does it look like?
const report = score(markup) // how good is it?
```
---
## Optimizer (injected) & rasterizer (default in-kit)
- **`optimize`** (Clean's diff signal) — **injected**. The optimizer is a
separate, not-yet-stable package, so the kit doesn't depend on it. Pass it into
`score`:
```ts
import { optimize } from 'gui-optimizer'
score(markup, { optimize }) // Clean scored fully
score(markup) // Clean reported NA — absent with a reason, never faked
```
- **rasterizer** (`render` → `preview.webp`) — **shipped, with a default**. The
kit rasterizes via `puppeteer-core` (a *light* install — no bundled Chromium —
that drives a system browser). Opt into full `puppeteer` with
`DOTGUI_PUPPETEER=full` for a self-contained Chromium. No browser found →
placeholder `preview.webp` + a console error, never a hard failure. Full design
and limitations: [rasterize](src/rasterize/README.md).
The kit stays pure and light; heavy or
volatile machinery is the consumer's choice.
---
## Where the AI lives (spoiler: not here)
AI — when it's involved at all — lives at **one place only: authoring the
markup.** And authoring is author-agnostic: the `.gui` can be written by an AI
agent *or* by a human using tools (the CLI, the app, an editor). The kit sits
entirely downstream and never knows, or cares, which one produced the file.
```mermaid
flowchart LR
AI(["🤖 AI agent
(host + skill)"]) --> M["✍️ .gui markup"]
H(["🧑 Human
(CLI / app / editor)"]) --> M
M --> K["@dotgui/kit
deterministic · no AI
validate → render → pack"]
K -.->|"advisory"| S["score · quality report"]
```
Everything to the right of the markup is pure and deterministic. Keeping the kit
AI-free means its behaviour is reproducible and testable, and the quality report
means the same thing no matter who — or what — wrote the file. The agent (or
person) generates; the kit just runs the rules.
---
## Who consumes it
Everything that touches `.gui` files is a thin consumer of this one engine:
| Consumer | Uses | Role |
|---|---|---|
| `@dotgui/cli` | parser · validate · render · score · package · lint · autofix · rasterize | command-line workflow; the AI-free deterministic toolchain |
| `gui-app` | render · validate · package · lint · autofix | the standalone app — authoring + the preview grid |
| `gui-figma` | parser · render | Figma plugin (import/round-trip) |
| `gui-landing` | render | marketing site live previews |
| `dotgui-vscode` | (planned) validate · render | editor diagnostics + preview |
| `gui-tools` *(future)* | render → PNG/JPG/PDF/SVG | export platform; more output formats on the same engine |
Each one imports the kit and stops reimplementing the engine. A fix to the parser
or renderer ships once, atomically, to all of them — no version cascade.
---
## Design principles
- **One package, one version.** The engine pieces are tightly coupled and move
together. No internal version drift.
- **Subpath exports + tree-shaking.** Use one slice, ship one slice.
- **Sensible defaults, swappable.** The rasterizer ships a `puppeteer-core`
default (light install) you can upgrade to full `puppeteer` or override; the
optimizer (unstable) is injected. Defaults work out of the box; nothing heavy is
forced.
- **HTML is the reference output.** Other formats are derived, and live outside
the kit.
See [`../ARCHITECTURE.md`](../ARCHITECTURE.md) for the full consolidation decision
and rationale.
---
# @dotgui/cli
The command-line toolchain for the dotgui format. `gui` reads, edits, validates, renders, and packages `.gui` files — and installs the dotgui **skill** into the AI tools you already use.
```
npm i -g @dotgui/cli
gui setup
```
---
## Mental model
**`gui` does not contain an AI.** It is a fast, deterministic tool for working with `.gui` files: lint, autofix, render, package. That's it.
The *design generation* happens in **your own agent** — Claude Code, Gemini CLI, Cursor, Codex, whatever you already use. `gui setup` installs the dotgui skill into those tools, so when you ask one to "make a stocks app in gui," it knows the format, writes the markup, and calls `gui` to validate and package the result.
Why this split: a real agent — warm session, your best model, full tool-calling and vision — produces better UIs than anything a CLI could wrap around a model. So the CLI doesn't try. It owns the one thing only it can do perfectly: keep `.gui` files **valid**.
```
You ──ask──▶ your agent ──uses──▶ dotgui skill ──calls──▶ gui (this tool)
└─ lint · autofix · render · package
```
---
## What a `.gui` is
A `.gui` is a **zip package**:
```
home.gui
├── design.guix # the markup (XML)
├── assets/ # images, SVGs
└── preview.webp # rendered thumbnail
```
You never unzip it by hand. `gui` treats a `.gui` like a folder — read the markup, list assets, add or remove an asset — and does the unzip/rezip for you in an **ephemeral shadow directory** that is created and deleted within each command. Nothing is left on disk.
---
## Commands
### Edit the markup
```bash
gui read home.gui # print the markup
gui write home.gui # read markup from stdin → lint → repack → regenerate preview
```
`gui write` is the save step: it lints, **exits non-zero with the errors** if the markup is invalid (nothing is written), and on success repacks the zip and regenerates `preview.webp` so it always matches. Pass `--no-preview` to skip the render for speed.
### Assets & preview (folder-like)
```bash
gui read home.gui --assets # list asset names + sizes
gui read home.gui --preview # extract preview, print a viewable path
gui read home.gui --asset hero.webp # extract one asset, print a viewable path
gui add home.gui logo.png [as brand.png]
gui rm home.gui assets/old.webp
gui set-preview home.gui shot.webp
```
`--preview` and `--asset` return a **file path** (in the shadow cache) so your agent can open the image with its own vision. Lists are printed as text.
### Check
```bash
gui lint home.gui # validate only
gui lint home.gui --fix # apply deterministic autofix, write back
```
### View
```bash
gui render home.gui -o out.png # rasterize to an image (PNG/WEBP/JPEG by extension)
gui open home.gui # open in the dotgui viewer
```
`render` (and the preview that `gui write` regenerates) drives a **Chromium-based
browser already installed** on your machine — Chrome, Edge, Brave, or Chromium —
via puppeteer-core (no bundled browser). Set `PUPPETEER_EXECUTABLE_PATH` to force a
specific one. If none is found, `render` fails with guidance and `gui write` simply
skips the preview rather than blocking the save.
### Inspect
```bash
gui info home.gui # name, platform, asset list, dimensions
```
### Packaging (for humans — agents never need these)
```bash
gui pack ./my-design home.gui # a markup+assets folder → .gui
gui unpack home.gui ./my-design # .gui → a real folder you can edit
```
### Setup
```bash
gui setup # install the skill into detected agents + allowlist `gui`
gui skill where # show where the skill is installed
gui skill update # refresh the skill after an upgrade
gui --version
gui help
```
---
## `gui setup`
Run once after install. It:
1. Detects installed agents (Claude Code, Gemini CLI / Antigravity, …).
2. Installs the dotgui skill into each one's discovery location (e.g. `~/.claude/skills/dotgui/`, the global Gemini skills dir).
3. Allowlists the `gui` command so the agent runs it **without permission prompts**.
It asks for one confirmation; it does not write anything silently. For agents without a skill model, it prints the snippet to add manually.
After setup, ask any of those tools for a UI "in gui" and it builds it — picking the skill automatically.
---
## Behaviors
- **Ephemeral shadow dir** — every asset/markup command unzips → changes → rezips → **deletes** the working dir before exiting. No leftover folders.
- **Self-cleaning** — the shadow dir lives in one fixed cache (`~/.cache/dotgui/`); on startup `gui` sweeps any stray dir older than a few hours, so a crashed command can't litter your filesystem.
- **Lint gates everything** — `write`, `add`, `set-preview`, and `pack` all lint before committing. Invalid markup never gets packaged; the command exits non-zero with structured errors.
- **Preview stays in sync** — `gui write` regenerates `preview.webp` on every successful save.
---
## Conventions
| Flag | Meaning |
|------|---------|
| `--json` | machine-readable output and errors (for agents) |
| `--fix` | apply deterministic autofix and write back |
| `-o, --out ` | output path |
| `--no-preview` | skip preview regeneration on write |
| `--quiet` | suppress non-essential output |
**Exit codes:** `0` success · `1` lint/validation error · `2` bad input or missing file · `3` user cancelled.
---
## Roadmap
- `gui mount home.gui` — a live folder that auto-syncs back into the zip (the only persistent mode; cleans up on unmount).
- `gui diff a.gui b.gui` — structural diff.
- `gui pull ` / `gui push home.gui ` — Figma round-trip.
---
# @dotgui/embed
Render `.gui` files anywhere in the browser. A thin layer over [`@dotgui/kit`](../kit)'s
renderer — one CDN script, **zero dependencies** for the consumer.
```html
```
One element does everything: **``**. It's the explicit "render a gui here"
declaration. The file comes from an inner `
```
Inline content lives in a `
```
**Sizing (CSS):**
- No CSS → sizes to the file's native dimensions, like an inline SVG.
- Width only → height follows the file's aspect ratio.
- Width + height → fixed canvas; the file is placed center-center, scaled to fit.
**Controls (individual booleans — present = on):**
| attr | default | effect |
|---|---|---|
| `zoom` | on | scroll/pinch-zoom + drag-pan, and a "Fit" reset button |
| `grid` | on | dot-grid canvas background (the dotgui surface) |
| `download` | off | download the `.gui` file |
| `copy` | off | copy the `.guix` markup to the clipboard |
| `mode-switch` | off | mode dropdown — only when the file declares >1 mode |
Default render mode comes from the file (RFC-0037 ``). Theme the grid with
`--gui-embed-bg`, `--gui-embed-dot`, `--gui-embed-dot-gap`.
## Programmatic use
The CDN build self-installs. As a module:
```js
import { install } from '@dotgui/embed'
install() // registers
```
## Build
```sh
bun run build # → dist/gui-embed.js (IIFE, CDN) + dist/gui-embed.esm.js
bun run typecheck
```
Everything (`@dotgui/kit` render + parser + package, panzoom, fflate) is bundled into
one self-contained file. Demo: `embed/demo/index.html`.