UXLoom Quickstart

What UXLoom is: the validation layer for UI/UX design. You (or your AI agent) design; UXLoom proves what's missing — unreachable screens, missing empty/loading/error states, WCAG contrast failures, undersized touch targets, labels that break under translation — before any code exists.

What UXLoom is not: it does not draw mockups or generate screens. It makes whatever designs your agent produces *complete and provable*. Think of it as the type-checker for UX.

Prerequisites: Node.js 20+. For the agent workflow: Claude Code, Codex CLI, or any MCP client. No account, no API key, MIT licensed.


Try it in 60 seconds (nothing to design yet)


git clone https://github.com/uxloom-dev/uxloom && cd uxloom
npx uxloom check examples/shopmweb/uxloom.generated.project.json

You'll see 9 errors a UI generator left behind. Now check the repaired version — this is what "done" looks like:


npx uxloom check examples/shopmweb/uxloom.project.json

Path A — design with Claude Code (the main workflow)

1. Connect UXLoom — one command sets up everything (MCP config, agent skill, starter file):


cd your-project
npx uxloom init

(Equivalent manual form: claude mcp add uxloom -- npx -y uxloom.)

2. Start Claude and ask for a design. Example first prompt:

Design the user journeys and screens for a habit-tracking mobile app
(log habits, streaks, reminders) using UXLoom. Iterate until validation
is clean.

3. What happens next (and what's expected of you):

most questions from context; only taste questions reach you (brand colors, tone). Claude reports the assumptions it made — correct any.

state contracts, then runs validation and fixes findings until the report is clean.

file is the design contract, versioned next to the code it specifies.

4. Build from the contract. The design now drives implementation:

Implement the HabitListScreen as a React component. Cover every state
in its uxloom contract: default, empty, loading, error.network.

5. Change safely. Any future request ("add a social sharing journey") goes through the same loop — validation catches what the change broke.

See the design — live wireframe mocks


npx uxloom preview        # opens live mocks at http://localhost:4400

Every screen and every contracted state rendered as wireframes (loading skeletons, empty placeholders, error banners appear automatically), on desktop/tablet/mobile frames, with clickable journey events. Keep it open while your agent designs — it updates in real time.

Verify any time, without the agent


npx uxloom check          # design completeness, exit 1 on errors
npx uxloom audit          # does the code implement the contract? exit 1 on drift

Path B — Codex CLI


codex mcp add uxloom -- npx -y uxloom

Same workflow. The skill ships in the npm package (skills/uxloom/) — copy it to .agents/skills/ for best results.

Share it — stakeholders need a link, not npx


npx uxloom export         # writes uxloom-preview.html — email it, host it

Design changes in PRs — readable, not JSON walls


npx uxloom diff --git main            # semantic diff vs main
npx uxloom diff old.json new.json --markdown   # PR-comment ready

Native apps too

The audit reads native markers: add // data-ux-screen: X and // data-ux-state: y comments (any language), or use .accessibilityIdentifier("ux-state:y") in SwiftUI / Modifier.testTag("ux-state:y") in Compose. With optional playwright (npm i -D playwright), npx uxloom audit --live http://localhost:3000 verifies markers in the real DOM, and npx uxloom export --png shots/ renders every screen×state to images. npx uxloom export --svg mocks/ needs nothing extra — the SVGs import straight into Figma or Penpot.

Designers: comment directly on the mocks — and hand them to the agent

In npx uxloom preview, toggle comment mode and click anywhere on a screen to leave feedback. Open comments appear in uxloom check as reviewer-comment warnings — your feedback enters the agent's fix loop, and you resolve it in the preview when it's addressed.

Click "→ agent" on any comment to assign it. The agent (any MCP client — Claude, Codex, anything) then reads the whole work packet with comment_context: the pinned layout block, the screen contract, the journey references, and the current findings for that screen. It makes the change and calls comment_resolve with a note explaining what it did — the pin clears live in your preview. One click turns a pinned note into addressed work.

Or edit directly: toggle edit mode (✎) to reorder blocks, rewrite copy and labels inline, add/remove blocks, and adjust design tokens — every change writes to the same project file your agent works from, validated before saving, live for every viewer.

Path C — CI gate (no agent involved)


# .github/workflows/design.yml
- run: npx uxloom check uxloom.project.json --github
- run: npx uxloom audit uxloom.project.json --github

Inline PR annotations via --github; --sarif for code scanning; --json for anything else. Adopting on an existing app? Run npx uxloom check --update-baseline once — existing findings are frozen as acknowledged debt and only new drift fails the build.


The rules that make it work

in requiredStates — or a written exemption when a state genuinely can't apply (a confirmation screen has no empty state). Findings can't be silenced by weakening the contract; UXLoom flags that too.

Troubleshooting

SymptomFix
no project file at ...Run from the directory containing uxloom.project.json, pass a path, or set UXLOOM_PROJECT
Agent designs screens without UXLoomSay "use UXLoom" explicitly, or install the skill from the npm package's skills/ folder
Findings feel noisy on a screenDon't delete required states — add an exemptions entry with a written reason

Full format and finding reference: packages/mcp-server/skills/uxloom/references/

JourneyGraph format reference (v0.1)

Contents

Modeling conventions (screens vs states)

Consistent granularity keeps designs comparable and critics meaningful:

panels, confirmation dialogs, and transient conditions are screen states, not separate screens. Examples: create (a creation modal on a list screen), key.issued-once (a shown-once panel), remove-confirm (a typed-confirmation dialog), identity.pending (an auto-refreshing tab condition).

create.error.slug-taken — the family prefix is what exemptions like error.any match on.

browse/manage journeys, the state where the user's goal is satisfied is final even if it has outgoing events.

Project shape


{
  "name": "shopfast",
  "formatVersion": "0.1",
  "platforms": ["web", "mweb", "ios", "android"],
  "journeys": [ ... ],
  "screens": [ ... ]
}

Stored at uxloom.project.json (override with UXLOOM_PROJECT env var). Plain JSON, versioned in git next to the code it specifies.

Journeys and target refs

A journey is a state machine. States reference screens; events move between states.


{
  "id": "checkout",
  "goal": "Returning shopper completes purchase in under 90 seconds",
  "entry": "cart",
  "states": {
    "cart":    { "screen": "CartScreen",
                 "on": { "CHECKOUT": "payment", "CART_EMPTY": "cart#empty" } },
    "payment": { "screen": "PaymentScreen",
                 "on": { "PAY": "confirm",
                         "CARD_DECLINED": "payment#error.declined",
                         "BACK": "cart" } },
    "confirm": { "screen": "ConfirmScreen", "final": true }
  }
}

Target refs: "payment" targets a journey state; "payment#error.declined" targets that state landing on a specific screen state. The screen state after # must be in the target screen's requiredStates or validation fails.

State ids: [a-zA-Z][\w-]* with optional dot-separated substates (error.network, error.declined).

Screens, contracts, and components


{
  "id": "PaymentScreen",
  "intent": "Collect payment with minimum anxiety",
  "requiredStates": ["default", "loading", "error.declined", "error.network"],
  "designedStates": ["default", "loading"],
  "platforms": ["mweb", "android"],
  "components": [
    {
      "semantic": "Button.Primary",
      "interactive": true,
      "minTargetPx": 48,
      "label": { "key": "checkout.pay", "en": "Pay now", "maxChars": 16 },
      "fg": "#FFFFFF",
      "bg": "#1D4ED8"
    }
  ],
  "exemptions": [
    { "state": "empty", "reason": "Payment form has no data-list to be empty." }
  ]
}

is progress (what exists so far). Validation errors on every gap.

never pixels or specific markup.

interactive one, and maxChars to every space-constrained label — the critics can only check what is declared.

Exemptions

An exemption documents why a baseline state (empty / loading / error.*) does not apply to a screen. The reason must be a real sentence (min 15 chars, schema-enforced). "error.any" (any error-prefixed state) exempts the error family. An exemption for a state that is also in requiredStates is flagged as contradictory.

Rich transitions, tokens, content, and team-scale features (v0.6)

Guards and roles — transitions accept an object form when a condition or role matters:


"on": {
  "DELETE": { "target": "confirm", "guard": "user.canDelete", "roles": ["admin"] },
  "BACK": "list"
}

Platform-scoped journeys — divergent mobile/desktop flows are separate journeys with "platforms": ["mweb"] on the journey.

Design tokens — project-level tokens theme the preview and document the system: { "colors": { "accent": "#2F6B52", "bg": "#FAF9F6", "surface": "#FFFFFF", "text": "#2B2725", "muted": "#7A716B" }, "radius": 8, "font": "Iowan Old Style, serif" }. Verify pairs with uxloom:palette_check.

Content-rich blocks — table blocks take columns: ["Recipient", "Status", "Sent"]; text/hero blocks take copy (real copy, not lorem); any block takes source naming its data binding. Screens take data ({ "messages": "Message[]", "filter": "StatusFilter" }) so implementers know the shape.

Fragments (team scale) — the base file may declare "include": ["designs/*.json"]; fragment files are { "journeys": [...], "screens": [...] } merged at load. Duplicate ids across files are errors. MCP tools write to the base file; fragments are edited as files.

Design rationale (evidence-based design, v0.8) — project, journeys, and screens carry the evidence behind decisions:


"rationale": {
  "decision": "Single-column checkout",
  "reasoning": "Completion task; category convention is a distraction-free linear flow…",
  "alternatives": [{ "option": "Two-column", "pros": ["cart visible"], "cons": ["collapses on mobile"] }],
  "sources": ["https://baymard.com/…"],
  "confidence": "high"
}

Adoption-gated enforcement: once any rationale exists (or config sets "rationale": "required"), undocumented decisions become rationale-missing warnings and weak ones rationale-thin (short reasoning, or no alternative with real pros AND cons). Process: design-intelligence.md; iterate with uxloom:design_review (max 3 rounds, enforced).

Config and baselineuxloom.config.json overrides thresholds ({ "thresholds": { "contrastRatio": 7, "expansionFactor": 1.5, "touchTargets": { "web": 44 } } }). uxloom check --update-baseline freezes existing findings into uxloom.baseline.json (brownfield adoption: block only new drift). Never baseline findings you can fix now.

Reviewer comments (agent-addressable, v0.9) — designers drop pinned comments in the preview; each pin records its screen, state, and the layout block it lands on. Open comments appear in validation as reviewer-comment warnings; comments the reviewer clicked "→ agent" on are *assigned* and appear first. The loop: uxloom:comments_list → uxloom:comment_context (full work packet: comment, anchored block, screen contract, journey refs, screen findings) → make the change → uxloom:comment_resolve with a real resolution note. Resolutions persist to <project>.comments.json and the pin clears live in every open preview. Never resolve without addressing.

Validation rules the schema enforces

of silently dropping data.

web | mweb | ios | android; requiredStates is non-empty.

Critics reference — finding codes, thresholds, and how to fix each

Contents

journey-completeness

Structural proofs over every journey. All findings are errors.

CodeMeaningFix
entry-missingentry names an undefined stateadd the state or change entry
screen-missingstate references an unregistered screenregister the screen
target-missingevent targets an undefined stateadd the state or retarget
target-state-missingstate#screenState ref where screenState is not in the target screen's requiredStatesadd it to requiredStates
dead-endnon-final state with no outgoing eventsmark final or add events (incl. BACK/CANCEL)
unreachablestate no user can ever arrive at (BFS from entry)add a transition to it or remove it
no-final-statejourney can never completemark at least one state final

state-coverage

The anti-happy-path critic.

CodeSeverityMeaning
state-undesignederrorrequiredState with no design yet
contract-driftwarningdesigned state missing from the contract
happy-path-contractwarningcontract lacks empty/loading/error.* without exemption
contradictory-exemptionwarningstate both exempted and required

Exemption policy: suppressing happy-path-contract requires a written reason per state (or error.any for the error family). Legitimate examples: terminal confirmation screens (no empty state), blank-by-definition forms (no empty state), static instruction screens (no loading). Illegitimate: "not needed", "later", any reason under 15 characters (schema rejects).

wcag-contrast

contrast-below-aa (error): components declaring both fg and bg are checked against WCAG 2.2 AA for normal text — 4.5:1 minimum, computed via relative luminance. Fix by darkening/lightening either side. Large-text (3:1) allowance is not yet modeled; when a component is genuinely large display text, note it and pick colors meeting 4.5:1 anyway.

touch-targets

target-too-small (error): interactive components with minTargetPx are checked per platform the screen ships on:

PlatformMinimumSource
ios44iOS HIG 44pt
android48Material 48dp
mweb44recommended touch web
web24WCAG 2.2 target-size AA

The target includes padding/hit-slop, not just the visible glyph.

text-expansion

label-overflow (warning): labels with maxChars are checked at ×1.4 — the standard pseudo-localization planning factor (German/Finnish run 30–40% longer than English for UI-length strings). Fix by shortening the English source or widening the layout budget.

Severity policy

must not stop while any remain).

reason — never ignore it silently.

Implementation audit — drift detection between contract and code

Contents

What the audit proves

uxloom:project_audit (or npx uxloom audit) checks whether the implementation actually contains each contracted screen state. Static analysis is honest about its limits: a state earns implemented only with marker evidence (file:line); files without markers yield unproven, never a false pass.

The marker convention

When implementing a screen from the contract, mark where each state renders — any framework, zero runtime cost:


<main data-ux-screen="MessageDetail">
  {isLoading && <Skeleton data-ux-state="loading" />}
  {error && <ErrorPanel data-ux-state="error.network" onRetry={retry} />}
  {message?.status === "parked" && <ParkedNotice data-ux-state="parked" />}
  {message && <Timeline data-ux-state="default" events={message.events} />}
</main>

the file without needing a registry entry.

state, exactly matching the contract's state ids.

Native platforms (Swift / Kotlin / Dart / Java)

The audit scans .swift, .kt, .kts, .dart, and .java sources too. Since native UI has no HTML attributes, three marker forms are recognized — all equal-weight tier-2 evidence, same verdicts, same file:line:

1. Attribute form (web, shown above): data-ux-screen="X" / data-ux-state="y".

2. Native identifiers — piggyback on the accessibility/test hooks you should be setting anyway:

SwiftUI:


VStack {
  Text("Inbox").accessibilityIdentifier("ux-screen:Inbox")
  if isLoading {
    ProgressView().accessibilityIdentifier("ux-state:loading")
  }
  List(rows) { RowView($0) }.accessibilityIdentifier("ux-state:default")
}

Jetpack Compose:


// data-ux-screen: Inbox
@Composable
fun InboxScreen(state: UiState) {
  when (state) {
    UiState.Loading -> Spinner(Modifier.testTag("ux-state:loading"))
    UiState.Empty -> EmptyCard(Modifier.testTag("ux-state:empty"))
    else -> MessageList(Modifier.testTag("ux-state:default"))
  }
}

3. Comment form — works in any language (Dart, Java, or anywhere an identifier can't be attached). // and single-line /* ... */ are both recognized; whitespace around the : is tolerated:


// data-ux-screen: Profile
Widget build(BuildContext context) {
  if (loading) {
    return Spinner(); // data-ux-state: loading
  }
  return ProfileBody(); // data-ux-state: default
}

Comment markers are declaration-grade evidence: they assert where a state renders but, unlike an attribute on an element, they cannot prove anything about the element itself — so the thin-marker check does not apply to them (nor to identifier markers). Live verification (the DOM tier) is the stronger tier when you need proof beyond declaration. The static-render check still applies to all forms: an unconditional loading/empty/error* marker is challenged whatever its spelling.

The registry (uxloom.map.json)

For files that can't carry a screen marker (or to scope shared files), map screens to path globs next to the project file:


{
  "MessageDetail": { "paths": ["app/dashboard/messages/[id]/**", "components/messages/*"] }
}

Globs support ** and *. A screen with neither markers nor matching registry paths is an error — it has no implementation at all.

Verdicts and finding codes

CodeSeverityMeaning
screen-unmappederrorno files at all for a contracted screen
state-unimplementederrorscreen uses markers, this state has none
state-unprovenwarningfiles exist but carry no markers — nothing verifiable

Marker quality (anti-washing)

A marker on an empty element proves nothing — "marker washing" is pasting data-ux-state attributes to turn verdicts green without implementing the states. Tier 2.5 runs static marker-quality heuristics that challenge suspicious markers. Heuristics challenge evidence, they never grant it: every check emits a warning with file:line evidence and never upgrades a verdict. They are conservative — when the source is ambiguous they stay silent.

CodeSeverityTriggered when
state-marker-thinwarningthe marker sits on a bare element that renders nothing: self-closing (<div data-ux-state="empty" />) or closing immediately with only whitespace inside, with no other props (no className, no children, no bindings). Attribute form only — comment and identifier markers cannot prove element emptiness, so they are never flagged thin
state-marker-duplicatewarningone element carries several data-ux-state attributes, or two different states are marked on textually identical bare elements in the same file — one element cannot render two distinct states distinctly; each state is flagged once. Attribute form only, for the same reason as thin
state-marker-staticwarninga conditional-by-nature state (loading, empty, or any error*) has no conditional-rendering signal (&&, ternary/?:, if, guard, switch, when, .let, v-if, *ngIf, {#if, .map(, optional chaining) on its line or the 3 lines above — the state is likely always- or never-rendered. Applies to all three marker forms. Not applied to default or custom states, and suppressed inside components named after the state (e.g. function LoadingSkeleton, struct LoadingView, fun EmptyState, class ErrorBanner), where the conditional lives at the call site

Fixing the warnings is always the same move: render the real state UI inside (or as) the marked element, gated by the condition that actually produces the state — never relocate the marker to silence the check. Static heuristics narrow washing; they cannot eliminate it — fixture and browser tiers (4–5) remain future work.

Workflow for agents

  1. When implementing from a contract: emit markers as you build each

state — the code becomes self-auditing for free.

  1. When auditing an existing codebase: run uxloom:project_audit; for

unproven screens, read the code, add markers where states genuinely render, re-run. States you cannot mark truthfully are your gap list.

  1. In CI: npx uxloom check && npx uxloom audit — design completeness

and implementation fidelity, both gated on exit codes.

Releasing — the surface-sync contract

Every capability change must reach every applicable surface. Nobody remembers this manually; the machine enforces it. This document is the map of surfaces and the process that keeps them coherent.

The principle

Same philosophy as the product: single source of truth, deterministic checks, CI gates. The source of truth for the tool list is the running MCP server (introspected, not grepped); for the version it is packages/mcp-server/package.json. Everything else is either *derived* (never hand-edited) or *enforced* (CI fails with the exact fix).

Surfaces

SurfaceSync mechanism
serverInfo version (server.ts)Derived — read from package.json at runtime
npm packagesAutomated — release pipeline publishes unpublished versions with provenance
MCP registry (server.json)Enforced — consistency check requires version match; release-prep bumps it
GitHub release + benchmark scorecardAutomated — release pipeline
Website version badge, copyEnforced — badge must match major.minor; stale claims ("pre-release", "coming soon") are banned; release-prep stamps the badge
docs/llms.txt tool listEnforced — must list every real tool
READMEs (root + packages)Enforced — tool table complete; CLI commands must exist; no rotting tool counts
Agent skill (skills/uxloom/)Enforced — may never reference a nonexistent tool; ships inside the npm package so it versions atomically
Benchmark claimsEnforced — bench runs on every push against a committed baseline; scorecard regenerated per release
Glama listingExternal, self-syncing — unpinned commit means their builds track main; after a significant release, optionally press Build & Release in the Glama admin to refresh their release object
awesome-mcp-servers entryExternal, manual — keep the entry evergreen (no version numbers, no tool counts); it should only need touching if positioning changes

The release flow (three commands)


node tools/release-prep.mjs 0.4.0   # bumps every surface, runs build+tests+consistency+bench
git add -A && git commit -m "Release v0.4.0: <what changed>" && git push
git tag v0.4.0 && git push origin v0.4.0   # pipeline: npm + registry + release w/ scorecard

The standing gates (every push, not just releases)

CI runs typecheck → tests → surface consistencybenchmark vs baseline. A PR that adds a tool without documenting it, documents a CLI command that doesn't exist, leaves a stale claim, or degrades a benchmark grade cannot merge. The failure message names the surface and the fix.

Writing rules that prevent drift at the source

"the tools" not "the 12 tools" — numbers rot, the checker bans them.

server.ts → row in packages/mcp-server/README.md → name in docs/llms.txt → skill reference if agents should use it → test.

the checker because futures become lies silently.

UXLoom positioning

The USP, in one sentence

UXLoom is the only design tool where "done" is provable — deterministic design contracts that make UI completeness a CI-gateable fact instead of an opinion.

The pain, with numbers

costs ~30× what it costs at design time. 62% of developers report redoing UI work due to design/communication gaps; a single product pod loses on the order of $300k/year to handoff inefficiency.

screens at unprecedented volume, so verification — not generation — is now the bottleneck. Nothing in the market proves what generated UI is missing.

project. There is no artifact both sides can hold each other to.

Why every alternative fails this specific job

AlternativeWhy it can't do this job
Figma / Penpot / canvas toolsStore pictures of decisions; a frame doesn't know its error state is missing and can't fail a build
v0 / Lovable / Figma Make / generatorsProduce the happy path; they are the reason the gap exists, not the check on it
"Ask the LLM to review the design"Non-deterministic: different answer every run, so it can never gate CI; findings are opinions without codes, locations, or fixes
Accessibility linters (axe, Stark)Check rendered output late, in QA; nothing exists at design time, and nothing covers journey completeness or state coverage at all

The three properties that make the moat

  1. Design as data (JourneyGraph). Journeys are state machines, screens

carry contracts, everything lives in git next to the code. Diffable, reviewable, agent-native.

  1. Deterministic critics. Same input, byte-identical report (benchmarked:

SHA-256-stable across processes). This single property is what turns design quality from a review comment into a merge gate.

  1. Honesty mechanics. Exemptions-with-reasons and happy-path-contract

detection mean the score can't be gamed by weakening the contract — the failure mode of every checklist tool.

Proof points (benchmarked, reproducible: packages/bench)

Message hierarchy

  1. "Your generator gave you 6 screens. UXLoom proves you're missing 9 states."
  2. "The happy path is not a product."
  3. "Deterministic, so it can gate CI — an LLM opinion can't."
  4. "Zero findings is reachable honestly: exempt with a reason, never silently."

RFC 0003 — The company release (v0.6.0)

Status: Shipping · Source: external-reviewer critique (2026-08-08)

Every drawback from the review maps to a requirement below. Requirements are grouped into three independent build lanes with strict file ownership.

R1 — Brownfield adoption: baseline + config *(review §6, ranked #1)*

(contrast ratio, localization expansion factor, per-platform touch-target minimums). Defaults unchanged. Unknown keys rejected.

that are acknowledged debt. Baselined findings are reported as a count, never fail the run. --update-baseline freezes current findings. The ESLint/Ruff adoption model: freeze today's debt, block only new drift.

R2 — Themed preview + shareable export *(review §1/§10, ranked #2)*

font): the preview applies them — branded mocks, not gray boxes, the moment the design system is declared. Absent tokens → current wireframe.

(embedded data, no server) — email it, host it, put it in a deck. Stakeholders need a link, not npx.

R3 — Designer feedback loop *(review §2, ranked #3)*

a note; pins render on the mock; comments persist to <project>.comments.json; resolve from the UI.

designer feedback enters the same loop agents already iterate on. Designers become participants with veto power, not spectators.

R4 — CI-native reporters *(review §7, ranked #4)*

(SARIF 2.1.0 for GitHub code scanning), --github (workflow-command annotations). Human output unchanged by default.

R5 — Anti-marker-washing (audit tier 2.5) *(review §5, ranked #5)*

and nothing else), duplicate markers (one element claiming states it cannot distinguish), statically-rendered "conditional" states (marker not under any conditional rendering signal). Heuristics report warnings with evidence — they never upgrade a verdict, only challenge one. Honest labels: this narrows washing; tiers 4–5 (fixture/browser) remain future.

R6 — Richer content contracts *(review §3, ranked #6)*

source (named data binding). Screens gain data (named field→type shape). The contract now carries what implementers actually fight about.

R7 — Flows that match reality + team-scale format *(review §6/§11, #7)*

and role-variants are expressible and render in the preview.

journeys scoped honestly.

({journeys?, screens?}) — teams split the design across files; duplicate ids across files are errors; MCP writes go to the base file.

Explicitly deferred (recorded, not hidden)

From the review's broader sections, consciously open after v0.6:

with veto power (comments); authoring remains agent/file-driven

sharing today

native-platform audit (SwiftUI/Compose markers) — tier 2.5 heuristics narrow washing on web; native audit needs per-platform runners

today's critics cover contrast + target size; broader WCAG modeling is a design problem, not a threshold

guards + data shapes carry intent; full behavior contracts are a format evolution

docs site — ecosystem maturity items

now states how much of the design was actually checkable.

Lanes and ownership

(includes+comments), critics options, CLI wiring, docs, integration.

preview-export.ts — theming, comments UI/API, static export.

skills/uxloom/references/audit.md additions.

RFC 0004 — The frontier release (v0.7.0)

Status: Shipping · Closes every deferred item from RFC 0003.

R8 — uxloom diff: human-reviewable design diffs *(Lane D)*

Semantic diff of two project files (or --git <ref> vs working tree): journeys/screens/states added/removed, transition changes (incl. guards/ roles), contract deltas, token changes, exemption changes. Output: human, --json, --markdown (PR-comment ready). A thousand-line JSON diff becomes ten meaningful lines.

R9 — Native-platform audit *(Lane E)*

Marker evidence in Swift/Kotlin/Dart/Java via language-agnostic comment markers (// data-ux-screen: X, // data-ux-state: y) and native idioms (.accessibilityIdentifier("ux-state:x"), Modifier.testTag("ux-state:x")). Audit scans native sources; marker-quality heuristics stay conservative on comment markers (no thin-check — comments can't prove emptiness; static- render check still applies). The web-only audit asterisk is gone.

R10 — Audit tier 4 (live DOM) + PNG export *(Lane F, optional Playwright)*

(uxloom.map.json gains optional route), verifies data-ux-screen presence and that marked state elements exist in the real DOM (default-visible states verified as rendered; others as present). Verdict tier "dom-verified" with honest labeling.

standalone HTML. Both features degrade gracefully with an install hint when Playwright is absent — the core stays zero-dependency.

R11 — Accessibility pack v1 *(Lane MAIN)*

Only honestly-checkable-at-design-time rules, no checkbox theater:

screen-reader users get nothing.

(completing WCAG 1.4.3 properly instead of over-flagging display text).

honor prefers-reduced-motion; "essential" documents intent.

R12 — Interaction-behavior specs *(Lane MAIN + G renders)*

Fields: validation { required?, pattern?, message? }. List/table blocks: sort, filter (column lists). Documented intent for codegen; rendered as chips in the preview.

R13 — Designer authoring: structured edit mode *(Lane G)*

In the preview: edit tokens, edit block copy/labels inline, reorder/add/ remove blocks, all POSTed to the server which writes the project file — the same file agents edit, watched live. Authoring without a canvas: designers manipulate the same source of truth, in design terms.

R14 — Figma/Penpot bridge via SVG *(Lane G)*

uxloom export --svg <dir>: one SVG per screen×state, faithful to the wireframe/theme renderer. Figma and Penpot import SVG natively — the bridge that works today with zero API coupling.

R15 — Real docs site *(Lane MAIN)*

uxloom.dev/docs: generated from the repo's own markdown (QUICKSTART, skill references, RFCs) by a zero-dep generator run in release-prep — docs can never drift from the shipped truth.

Still not engineerable

Ecosystem maturity (users, adoption, bus factor) — only distribution and time. Recorded, unbuildable.