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):
- UXLoom interviews the agent through a structured brief. Claude answers
most questions from context; only taste questions reach you (brand colors, tone). Claude reports the assumptions it made — correct any.
- Claude defines journeys (as state machines), registers screens with
state contracts, then runs validation and fixes findings until the report is clean.
- You get
uxloom.project.jsonin your project — commit it. That
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
- Every screen belongs to a journey. No orphan screens; flows first.
- Contracts are honest. Every screen needs empty/loading/error states
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.
- Zero errors is the exit condition.
uxloom checkexits 1 otherwise.
Troubleshooting
| Symptom | Fix |
no project file at ... | Run from the directory containing uxloom.project.json, pass a path, or set UXLOOM_PROJECT |
| Agent designs screens without UXLoom | Say "use UXLoom" explicitly, or install the skill from the npm package's skills/ folder |
| Findings feel noisy on a screen | Don'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)
- Project shape
- Journeys and target refs
- Screens, contracts, and components
- Exemptions
- Validation rules the schema enforces
Modeling conventions (screens vs states)
Consistent granularity keeps designs comparable and critics meaningful:
- A screen is a destination — a page, route, or full view a user lands on.
- A state is a condition of that destination — modals, drawers, tabs,
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).
- Dot-namespace variants:
error.network,error.validation,
create.error.slug-taken — the family prefix is what exemptions like error.any match on.
- Journeys end. Every journey needs at least one
finalstate — for
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." }
]
}
requiredStatesis the contract (what production needs);designedStates
is progress (what exists so far). Validation errors on every gap.
platformsdefaults to the project's platforms when omitted.semanticnames a role (Button.Primary,List.Selectable,Nav.Tabs) —
never pixels or specific markup.
- Give
fg/bgto every text-bearing component,minTargetPxto every
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 baseline — uxloom.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
- Unknown fields are rejected (strict schemas) — a typo fails loudly instead
of silently dropping data.
- Colors are hex (
#RGBor#RRGGBB); platforms are one of
web | mweb | ios | android; requiredStates is non-empty.
Critics reference — finding codes, thresholds, and how to fix each
Contents
- journey-completeness (6 error codes)
- state-coverage (2 errors, 2 warnings, exemption policy)
- wcag-contrast
- touch-targets
- text-expansion
- Severity policy
journey-completeness
Structural proofs over every journey. All findings are errors.
| Code | Meaning | Fix |
entry-missing | entry names an undefined state | add the state or change entry |
screen-missing | state references an unregistered screen | register the screen |
target-missing | event targets an undefined state | add the state or retarget |
target-state-missing | state#screenState ref where screenState is not in the target screen's requiredStates | add it to requiredStates |
dead-end | non-final state with no outgoing events | mark final or add events (incl. BACK/CANCEL) |
unreachable | state no user can ever arrive at (BFS from entry) | add a transition to it or remove it |
no-final-state | journey can never complete | mark at least one state final |
state-coverage
The anti-happy-path critic.
| Code | Severity | Meaning |
state-undesigned | error | requiredState with no design yet |
contract-drift | warning | designed state missing from the contract |
happy-path-contract | warning | contract lacks empty/loading/error.* without exemption |
contradictory-exemption | warning | state 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:
| Platform | Minimum | Source |
| ios | 44 | iOS HIG 44pt |
| android | 48 | Material 48dp |
| mweb | 44 | recommended touch web |
| web | 24 | WCAG 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
- error = provably broken for users; blocks (CLI exits 1; validation loop
must not stop while any remain).
- warning = judgment call surfaced; resolve it or exempt it with a
reason — never ignore it silently.
Implementation audit — drift detection between contract and code
Contents
- What the audit proves
- The marker convention (how to make code self-auditing)
- Native platforms (Swift / Kotlin / Dart / Java)
- The registry (uxloom.map.json)
- Verdicts and finding codes
- Marker quality (anti-washing)
- Workflow for agents
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>
data-ux-screen="<ScreenId>"once per screen component — it also maps
the file without needing a registry entry.
data-ux-state="<state>"on the element that renders each contracted
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
| Code | Severity | Meaning |
screen-unmapped | error | no files at all for a contracted screen |
state-unimplemented | error | screen uses markers, this state has none |
state-unproven | warning | files 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.
| Code | Severity | Triggered when |
state-marker-thin | warning | the 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-duplicate | warning | one 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-static | warning | a 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
- When implementing from a contract: emit markers as you build each
state — the code becomes self-auditing for free.
- 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.
- 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
| Surface | Sync mechanism |
serverInfo version (server.ts) | Derived — read from package.json at runtime |
| npm packages | Automated — release pipeline publishes unpublished versions with provenance |
MCP registry (server.json) | Enforced — consistency check requires version match; release-prep bumps it |
| GitHub release + benchmark scorecard | Automated — release pipeline |
| Website version badge, copy | Enforced — badge must match major.minor; stale claims ("pre-release", "coming soon") are banned; release-prep stamps the badge |
docs/llms.txt tool list | Enforced — 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 claims | Enforced — bench runs on every push against a committed baseline; scorecard regenerated per release |
| Glama listing | External, 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 entry | External, 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 consistency → benchmark 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
- Never hardcode: versions, tool counts, or dates in prose. Say
"the tools" not "the 12 tools" — numbers rot, the checker bans them.
- New tool checklist (enforced, listed here for humans): register in
server.ts → row in packages/mcp-server/README.md → name in docs/llms.txt → skill reference if agents should use it → test.
- New CLI command: implement in
cli.tsusage → QUICKSTART → README. - Copy states facts, not futures: "lands soon" phrasing is banned by
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
- Design errors drive ~68% of rework cost; fixing a problem after shipping
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.
- AI generation made this worse, not better: generators produce happy-path
screens at unprecedented volume, so verification — not generation — is now the bottleneck. Nothing in the market proves what generated UI is missing.
- 83% of designers report design/code divergence; 26% see it on every
project. There is no artifact both sides can hold each other to.
Why every alternative fails this specific job
| Alternative | Why it can't do this job |
| Figma / Penpot / canvas tools | Store pictures of decisions; a frame doesn't know its error state is missing and can't fail a build |
| v0 / Lovable / Figma Make / generators | Produce 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
- Design as data (JourneyGraph). Journeys are state machines, screens
carry contracts, everything lives in git next to the code. Diffable, reviewable, agent-native.
- 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.
- 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)
- Critic precision 1.000 / recall 1.000 against a 13-defect seeded catalog
- Byte-identical reports across 25 runs and independent processes
- 1000-screen project validated in under 5ms; CLI cold start under 100ms
- 500 fuzzed inputs, zero crashes — malformed data fails loudly, never silently
Message hierarchy
- "Your generator gave you 6 screens. UXLoom proves you're missing 9 states."
- "The happy path is not a product."
- "Deterministic, so it can gate CI — an LLM opinion can't."
- "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)*
uxloom.config.jsonnext to the project file: configurable thresholds
(contrast ratio, localization expansion factor, per-platform touch-target minimums). Defaults unchanged. Unknown keys rejected.
uxloom.baseline.json: fingerprinted findings (check and audit sections)
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)*
- Project-level
tokens(colors: accent/bg/surface/text/muted, radius,
font): the preview applies them — branded mocks, not gray boxes, the moment the design system is declared. Absent tokens → current wireframe.
uxloom export [file] [--out path]: one self-contained static HTML file
(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)*
- Comment mode in the preview: click a location on any screen/state, leave
a note; pins render on the mock; comments persist to <project>.comments.json; resolve from the UI.
- Open comments surface in
uxloom checkasreviewer-commentwarnings —
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)*
checkandauditgain--json(stable machine schema),--sarif
(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)*
- Static marker-quality analysis: thin markers (element carries the marker
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)*
- Blocks gain
columns(tables),copy(real text, not just labels),
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)*
- Transitions accept object form
{ target, guard?, roles? }— conditions
and role-variants are expressible and render in the preview.
- Journeys accept
platforms— divergent mobile/desktop flows are separate
journeys scoped honestly.
includeglobs in the project file merge fragment files
({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:
- Figma/Penpot bridge, drag-editing canvas — designers can now review
with veto power (comments); authoring remains agent/file-driven
- PNG/PDF export — needs a browser dependency; static HTML covers
sharing today
- Audit tiers 4–5 (fixture rendering, browser verification) and
native-platform audit (SwiftUI/Compose markers) — tier 2.5 heuristics narrow washing on web; native audit needs per-platform runners
- Accessibility breadth (focus order, keyboard navigation, motion) —
today's critics cover contrast + target size; broader WCAG modeling is a design problem, not a threshold
- Interaction-behavior specs (sort/filter/validation semantics) —
guards + data shapes carry intent; full behavior contracts are a format evolution
uxloom diff(human-reviewable design diffs for PRs) and a real
docs site — ecosystem maturity items
- ~~Declaration-coverage blindness~~ — closed in v0.6.1: the report
now states how much of the design was actually checkable.
Lanes and ownership
- Lane MAIN: schema/types, config, baseline, reporters, loader
(includes+comments), critics options, CLI wiring, docs, integration.
- Lane B (agent):
preview.ts,preview-template.ts, new
preview-export.ts — theming, comments UI/API, static export.
- Lane C (agent): new
audit-tier3.ts(pure functions) + tests +
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 audit --live <baseUrl>: loads each screen's route
(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.
uxloom export --png <dir>: renders every screen×state to PNG via the
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:
unlabeled-interactive(warning): interactive component with no label —
screen-reader users get nothing.
- Large-text contrast:
textRole: "large"components check at 3:1
(completing WCAG 1.4.3 properly instead of over-flagging display text).
motion-fallback(warning):motion: "decorative"components must
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.