Sign In

uxloom

Package Overview
Dependencies
Maintainers
1
Versions
21
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

uxloom - npm Package Compare versions

Comparing version
0.7.0
to
0.8.0
+102
skills/uxloom/references/design-intelligence.md
# Design intelligence — the evidence-based design process
## Contents
- The principle
- Phase 1: research protocol
- Phase 2: decision framework
- Phase 3: recording rationale
- Phase 4: the review loop (max 3 rounds, enforced)
- Phase 5: the confidence report
- Citation discipline
## The principle
Users often don't know what they want, and a design with no argument
behind it is a guess wearing a suit. Your job is to make every major
decision **defensible**: researched, compared against alternatives, and
recorded in the contract so the user can inspect the why. UXLoom enforces
this once adopted (`rationale-missing` / `rationale-thin` findings) and
bounds the iteration (`uxloom:design_review`, max 3 rounds).
## Phase 1: research protocol (before designing anything)
1. **Classify the product** — category, primary persona, platform norms.
2. **Scan the market**: identify 3–5 established products in the category.
Use live web research when you have it; otherwise ground yourself in
[references/patterns.md](patterns.md) — curated conventions from
published usability research.
3. **Extract conventions**: for this category, what do users already know?
Navigation model, IA depth, critical flows, expected states, trust
signals, conversion patterns. Convention is a *default with evidence* —
users bring habits from every other product they use.
4. **Note deliberate-divergence candidates**: where might breaking
convention serve this product's specific goal? Divergence is allowed;
*unargued* divergence is not.
## Phase 2: decision framework (for every major decision)
Major decisions: information architecture, navigation model, each
journey's shape, each key screen's layout pattern, color direction,
density/tone. For each:
1. Generate **at least two genuine alternatives** (three for IA and
navigation). "Genuine" = you could defend shipping either.
2. Compare against fixed criteria: user's primary goal on this screen ·
category convention (match or argued divergence) · platform norms
(HIG/Material/web) · accessibility cost · implementation cost ·
localization risk.
3. Decide, and record honestly — including what the rejected option did
*better* (its pros). A comparison where the loser has no pros is
theater, and the critic will flag it.
## Phase 3: recording rationale
Attach `rationale` to the project (IA/brand direction), every journey
(flow shape), and every screen (layout/pattern choice):
```json
"rationale": {
"decision": "Single-column checkout with progress indicator",
"reasoning": "Checkout is a completion task, not a browsing task: the category convention is a distraction-free linear flow with visible progress. Multi-column layouts increase field-scanning errors on mobile, and this product is mweb-first.",
"alternatives": [
{ "option": "Two-column with persistent cart summary",
"pros": ["cart always visible", "fewer scrolls on desktop"],
"cons": ["competes for attention with form fields", "collapses poorly to mobile", "diverges from category convention without a driving reason"] }
],
"sources": ["https://baymard.com/blog/checkout-flow-average-form-fields"],
"confidence": "high"
}
```
## Phase 4: the review loop (max 3 rounds — the tool enforces this)
After the design validates clean, call `uxloom:design_review`. It returns
a six-line rubric (completeness, evidence, consistency, market fit,
accessibility, honesty), your rationale coverage, and the delta since the
last round. Critique the design against **every** rubric line, fix what
you find, call again. Three rounds maximum; the tool refuses a fourth —
at that point diminishing returns are real and the user's judgment beats
another self-review. Do not game the loop by making trivial rounds.
## Phase 5: the confidence report (what the user receives)
End with a structured summary the user can trust:
- **Decisions**: each major choice in one line, with confidence level.
- **The road not taken**: rejected alternatives and why — this is what
makes the user trust the chosen path.
- **Market grounding**: which conventions were followed, which were
deliberately broken, and the argument for each break.
- **Sources**: every factual claim's citation; judgment calls labeled as
judgment.
- Remind the user: every rationale is also in the preview (ⓘ) and in
exports — stakeholders see the evidence, not just the pixels.
## Citation discipline (non-negotiable)
- **Never fabricate statistics.** No invented percentages, no fake study
names. Cite research bodies (Baymard Institute, Nielsen Norman Group,
platform HIGs) for the *qualitative conventions* they actually document,
or real URLs you actually found in live research.
- A claim without a source is a **judgment call** — label it as one and
set confidence accordingly. Honest "medium confidence, judgment" beats
fake authority every time.
# Market pattern library — category conventions with grounding
Curated qualitative conventions from published usability research and
platform guidelines. Use as the offline grounding for Phase 1 research;
supplement with live web research when available. These are documented
conventions, not statistics — cite the research body, never invent numbers.
## Contents
- E-commerce & checkout
- SaaS dashboards & admin tools
- Onboarding & signup
- Content & media
- Messaging & social
- Booking & scheduling
- Cross-category invariants
## E-commerce & checkout
*(grounding: Baymard Institute checkout usability research; platform HIGs)*
- **Guest checkout is expected**; forced account creation is among the
most-documented abandonment causes. Offer account creation *after*
purchase.
- **Linear, single-column checkout** with visible progress; distraction is
the enemy of completion. Persistent cart summaries belong on desktop
only, subordinated to the form.
- **Trust signals near payment**: recognizable payment marks, clear error
recovery on declined cards (state `error.declined` with a path forward,
never a dead end).
- Cart is a *screen with an empty state that sells* (first-run guidance),
not a blank void. Promo codes hidden behind a link — an open field
invites coupon-hunting abandonment.
- Mobile: sticky primary CTA, 44px+ targets, numeric keyboards for card
fields (document in `data`/`validation`).
## SaaS dashboards & admin tools
*(grounding: Nielsen Norman Group on dashboards and complex apps)*
- **Overview-first IA**: land on "what needs attention", not settings.
Left-rail navigation for ≥5 sections; top tabs only for ≤4.
- **Dense tables are the workhorse**: real columns, sort/filter intent
declared, URL-driven filter state (shareable views).
- **The "one-click answers why" detail page**: every list row opens a
detail with a full event history/timeline. Status pills use semantic
colors distinct from the brand accent.
- Empty states are onboarding: first-run screens show the path to value,
never a blank table.
- Destructive actions: typed confirmation for irreversible operations;
plain confirm for recoverable ones.
## Onboarding & signup
*(grounding: NN/g on forms and onboarding; growth-team conventions)*
- **Minimize fields before value**: ask only what the first session needs;
defer profile completeness. Every extra field is measurable friction.
- SSO first-position when offered; email fallback always present.
- **Verification must not dead-end**: resend, change-email, and expired
states are part of the contract (`error.expired`, RESEND/CHANGE_EMAIL
events).
- Skippable steps must be honestly skippable ("Skip" without guilt copy).
- First-run destination shows the path to the product's core value in one
action — not an empty dashboard.
## Content & media
- Card grids for browsing, list rows for scanning; infinite scroll for
leisure consumption, pagination for reference/return tasks.
- Reader modes: typography-first, chrome recedes; progress indication for
long content.
- Search is a first-class journey (with `empty` no-results state that
suggests, not shrugs).
## Messaging & social
- Newest-at-bottom for conversation (chat mental model), newest-at-top
for feeds. Compose is persistent, never behind navigation.
- Optimistic send with honest failure recovery (`error.send` state with
retry — never silently drop a message).
- Presence/read state only where the product's trust model supports it.
## Booking & scheduling
- **Availability before commitment**: show open slots before asking for
details. Calendar pickers on desktop; wheel/list pickers on native
mobile (platform norms).
- Time zones explicit whenever parties can differ; confirmation screens
restate everything (what/when/where/cancellation terms).
- Offline/degraded matters: booking flows are used in transit —
`error.offline` states with preserved input are category table stakes.
## Cross-category invariants (always apply)
- Platform navigation norms win by default: bottom tabs (iOS) / bottom
nav or drawer (Android) / top or left nav (web) — divergence needs an
argument.
- Every network-touching screen contracts loading and error states; every
list contracts empty. (UXLoom enforces this; the convention is *why*.)
- Users read F-pattern on dense screens: primary action top-left-weighted
on LTR web, thumb-reachable bottom on mobile.
- Copy is design: buttons say what they do; errors say what went wrong
and how to fix it. (See HIG/Material writing guidance.)
- When two category conventions conflict, the user's primary goal on that
screen decides — record the conflict in the rationale's alternatives.
+8
-3
import { relative, resolve } from "node:path";
import { createRequire } from "node:module";
import { critique } from "@uxloom/critics";
import { applyBaseline, commentFindings, fingerprint, loadWorkspace, saveBaseline, } from "./workspace.js";
import { critique, rationaleCoverage } from "@uxloom/critics";
import { applyBaseline, commentFindings, criticOptionsFor, fingerprint, loadWorkspace, saveBaseline, } from "./workspace.js";
import { parseFormatFlag, render } from "./reporters.js";

@@ -40,3 +40,3 @@ const { version: VERSION } = createRequire(import.meta.url)("../package.json");

}
const report = critique(ws.project, ws.config.thresholds);
const report = critique(ws.project, criticOptionsFor(ws.config));
const all = [

@@ -97,2 +97,7 @@ ...ws.loadFindings,

(undeclared > 0 ? yellow(` (${undeclared} undeclared — the critics cannot see what isn't declared)`) : ""));
const rc = rationaleCoverage(ws.project);
if (rc.optedIn || ws.config.rationale === "required") {
console.log(`design rationale: ${rc.documented}/${rc.total} decisions documented` +
(rc.documented < rc.total ? yellow(" (evidence-based design adopted — document the rest)") : green(" ✔ fully evidenced")));
}
if (suppressed > 0)

@@ -99,0 +104,0 @@ console.log(dim(`${suppressed} finding(s) suppressed by baseline`));

@@ -1,1 +0,1 @@

{"version":3,"file":"check.js","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"check.js","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"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"}

@@ -12,6 +12,11 @@ /**

*
* When the project, a journey, or a screen carries design rationale
* (R20), an "ⓘ why" toggle reveals the evidence panel: decision,
* reasoning, alternatives argued, sources, and a confidence chip.
*
* renderStandalone() turns this same template into a static, shareable
* HTML file: project data embedded, SSE and comment mode removed.
* HTML file: project data embedded, SSE and comment mode removed. The
* rationale panel is kept — it is the stakeholder-confidence deliverable.
*/
export declare const PREVIEW_TEMPLATE = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>UXLoom preview</title>\n<style>\n :root {\n --bg: #eceeec; --panel: #f8f9f8; --ink: #2a2e2a; --dim: #6b706b;\n --line: #d2d6d2; --block: #ffffff; --blockline: #c4c9c4;\n --accent: #2f6b52; --warn: #b45309; --err: #b04338;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font: 14px/1.5 -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif;\n background: var(--bg); color: var(--ink); height: 100vh; display: flex; }\n button { font: inherit; cursor: pointer; background: none; border: none; color: inherit; }\n\n /* sidebar */\n aside { width: 250px; flex-shrink: 0; background: var(--panel); border-right: 1px solid var(--line);\n overflow-y: auto; padding: 14px; }\n aside h1 { font-size: 15px; margin-bottom: 2px; }\n aside .sub { color: var(--dim); font-size: 12px; margin-bottom: 14px; }\n aside h2 { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim);\n margin: 14px 0 6px; }\n .jname { font-weight: 600; font-size: 13px; margin-top: 8px; }\n .jplat { color: var(--dim); font-weight: 400; font-size: 11px; margin-left: 5px; }\n .jstate, .snav { display: block; width: 100%; text-align: left; padding: 3px 8px; border-radius: 6px;\n font-size: 13px; color: var(--ink); }\n .jstate:hover, .snav:hover { background: #e6e9e6; }\n .jstate.on, .snav.on { background: var(--accent); color: #fff; }\n .jstate small { color: inherit; opacity: .65; }\n .final-flag { opacity: .6; font-size: 11px; }\n\n /* main */\n main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }\n .bar { display: flex; align-items: center; gap: 8px; padding: 10px 16px;\n border-bottom: 1px solid var(--line); background: var(--panel); flex-wrap: wrap; }\n .bar .grow { flex: 1; }\n .chip { font-size: 12px; color: var(--dim); border: 1px solid var(--line); border-radius: 999px;\n padding: 2px 10px; }\n .chip.live { border-color: var(--accent); color: var(--accent); }\n .vp, .tab { padding: 4px 10px; border-radius: 6px; font-size: 13px; border: 1px solid transparent; }\n .vp.on { background: var(--ink); color: #fff; }\n .tab { border-color: var(--line); }\n .tab.on { background: var(--accent); color: #fff; border-color: var(--accent); }\n .tab.undesigned { border-style: dashed; color: var(--dim); }\n .tabs { display: flex; gap: 6px; padding: 10px 16px 0; flex-wrap: wrap; }\n\n .stage { flex: 1; overflow: auto; display: flex; align-items: flex-start; justify-content: center;\n padding: 26px; }\n .stage.commenting .screen { cursor: crosshair; }\n .frame { background: #fff; border: 1px solid var(--line); border-radius: 12px;\n box-shadow: 0 8px 30px rgba(0,0,0,.08); overflow: hidden; flex-shrink: 0; }\n .frame.desktop { width: 960px; } .frame.tablet { width: 640px; } .frame.mobile { width: 390px; }\n .chrome { display: flex; align-items: center; gap: 6px; padding: 8px 12px; background: #eef0ee;\n border-bottom: 1px solid var(--line); font-size: 11px; color: var(--dim); }\n .dot { width: 9px; height: 9px; border-radius: 50%; background: #cfd3cf; }\n .chrome .url { flex: 1; background: #fff; border: 1px solid var(--line); border-radius: 6px;\n padding: 2px 10px; text-align: center; }\n .notch { flex: 1; text-align: center; }\n .screen { position: relative; padding: 14px; min-height: 420px; display: flex;\n flex-direction: column; gap: 10px;\n background: var(--mock-bg, transparent);\n color: var(--mock-text, var(--ink));\n font-family: var(--mock-font, inherit); }\n\n /* wireframe blocks \u2014 themed via --mock-* custom properties when the\n project declares tokens; grayscale defaults otherwise */\n .b { border: 1.5px solid var(--blockline); border-radius: var(--mock-radius, 8px);\n background: var(--block); padding: 10px 12px; position: relative; }\n .b .lab { font-size: 12px; color: var(--mock-muted, var(--dim)); }\n .b-header, .b-nav, .b-footer { background: #f1f3f1; display: flex; gap: 10px; align-items: center; }\n .b-nav .pill, .b-header .pill { width: 54px; height: 8px; border-radius: 4px; background: var(--blockline); }\n .b-hero { min-height: 90px; display: flex; align-items: center; justify-content: center; }\n .b-hero .copy { font-size: 17px; font-weight: 600; text-align: center; }\n .b-text .ln { height: 8px; border-radius: 4px; background: #e0e3e0; margin: 7px 0; }\n .b-text .ln:last-child { width: 60%; }\n .copy { font-size: 13px; white-space: pre-wrap; }\n .b-image { min-height: 80px;\n background: repeating-linear-gradient(45deg, #f4f5f4, #f4f5f4 8px, #e8eae8 8px, #e8eae8 9px);\n display: flex; align-items: center; justify-content: center; }\n .b-button { display: inline-block; background: var(--mock-accent, var(--ink)); color: #fff;\n border-radius: var(--mock-radius, 8px);\n padding: 9px 18px; font-size: 13px; align-self: flex-start; border: none; }\n .b-field { background: var(--block); } .b-field .inp { height: 30px; border: 1.5px solid var(--blockline);\n border-radius: 6px; margin-top: 5px; background: #fdfdfd; }\n .row { display: flex; gap: 10px; align-items: center; border: 1.5px solid var(--blockline);\n border-radius: var(--mock-radius, 8px); padding: 9px 12px; background: var(--block); }\n .row .av { width: 26px; height: 26px; border-radius: 50%; background: #e0e3e0; flex-shrink: 0; }\n .row .ln { height: 8px; border-radius: 4px; background: #e0e3e0; flex: 1; }\n .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }\n .tbl { border: 1.5px solid var(--blockline); border-radius: var(--mock-radius, 8px);\n overflow: hidden; background: var(--block); }\n .tbl .tr { display: flex; border-top: 1px solid #e4e7e4; }\n .tbl .tr:first-child { border-top: none; background: #f1f3f1; }\n .tbl .td { flex: 1; padding: 8px; } .tbl .td .ln { height: 8px; border-radius: 4px; background: #e0e3e0; }\n .tbl .td.hd { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mock-muted, var(--dim)); }\n .srcchip { display: inline-block; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 10px; color: var(--mock-muted, var(--dim)); border: 1px solid var(--blockline);\n border-radius: 4px; padding: 0 5px; margin-top: 5px; align-self: flex-start; }\n .srcchip + .srcchip { margin-left: 4px; }\n .kids { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }\n\n /* state treatments */\n .skel .b, .skel .row, .skel .tbl { border-color: #e4e7e4; }\n .skel .ln, .skel .pill, .skel .av, .skel .inp, .skel .b-image, .skel .b-button, .skel .copy {\n background: linear-gradient(90deg, #ececec 25%, #f7f7f7 50%, #ececec 75%);\n background-size: 200% 100%; animation: shimmer 1.4s infinite; color: transparent; border-color: transparent; }\n @keyframes shimmer { to { background-position: -200% 0; } }\n @media (prefers-reduced-motion: reduce) { .skel * { animation: none !important; } }\n .banner { border: 1.5px solid var(--err); background: #fdf3f2; color: var(--err);\n border-radius: 8px; padding: 10px 12px; font-size: 13px; }\n .dimmed { opacity: .35; pointer-events: none; }\n .emptybox { border: 2px dashed var(--blockline); border-radius: 10px; padding: 34px 16px;\n text-align: center; color: var(--mock-muted, var(--dim)); }\n .overlay { position: absolute; inset: 0; background: rgba(42,46,42,.35); display: flex;\n align-items: center; justify-content: center; border-radius: 0 0 10px 10px; }\n .modal { background: var(--block); border-radius: var(--mock-radius, 12px); padding: 18px;\n width: min(85%, 340px); box-shadow: 0 12px 40px rgba(0,0,0,.25); }\n .modal h4 { font-size: 13px; margin-bottom: 8px; }\n\n /* events + meta */\n .meta { border-top: 1px solid var(--line); background: var(--panel); padding: 10px 16px;\n display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }\n .meta .lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim); }\n .ev { border: 1px solid var(--accent); color: var(--accent); border-radius: 999px;\n padding: 3px 12px; font-size: 12.5px; }\n .ev:hover { background: var(--accent); color: #fff; }\n .ev small { opacity: .7; }\n .ev .g { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10.5px;\n opacity: .85; margin-left: 4px; }\n .note { font-size: 12px; color: var(--warn); }\n .datameta { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px;\n color: var(--dim); }\n .err-load { margin: auto; color: var(--err); }\n\n /* comment mode */\n .cbtn { border: 1px solid var(--line); border-radius: 999px; padding: 3px 12px; font-size: 12.5px; }\n .cbtn.on { background: var(--accent); color: #fff; border-color: var(--accent); }\n .cbtn .cnt { display: inline-block; margin-left: 6px; min-width: 17px; text-align: center;\n border-radius: 999px; background: var(--accent); color: #fff; font-size: 11px; padding: 0 4px; }\n .cbtn.on .cnt { background: #fff; color: var(--accent); }\n .pinwrap { position: absolute; transform: translate(-50%, -50%); z-index: 5; }\n .pin { width: 20px; height: 20px; border-radius: 50%; background: var(--accent); color: #fff;\n font-size: 11px; line-height: 20px; text-align: center; padding: 0;\n box-shadow: 0 2px 6px rgba(0,0,0,.35); }\n .pinpop { display: none; position: absolute; top: 24px; left: -4px; background: #fff;\n color: var(--ink); border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px;\n width: 210px; font-size: 12.5px; box-shadow: 0 8px 24px rgba(0,0,0,.2); z-index: 6;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif; }\n .pinwrap:hover .pinpop, .pinwrap.open .pinpop, .pinwrap:focus-within .pinpop { display: block; }\n .pinpop .resolve { margin-top: 6px; border: 1px solid var(--accent); color: var(--accent);\n border-radius: 6px; padding: 2px 10px; font-size: 12px; }\n .pinpop .resolve:hover { background: var(--accent); color: #fff; }\n .cform { position: absolute; z-index: 7; background: #fff; border: 1px solid var(--line);\n border-radius: 8px; padding: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.25); width: 230px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif; }\n .cform input { width: 100%; font: inherit; font-size: 12.5px; padding: 4px 6px;\n border: 1px solid var(--line); border-radius: 6px; }\n .cform .acts { display: flex; gap: 6px; margin-top: 6px; justify-content: flex-end; }\n .cform .acts button { font-size: 12px; border: 1px solid var(--line); border-radius: 6px; padding: 2px 10px; }\n .cform .acts .save { background: var(--accent); color: #fff; border-color: var(--accent); }\n\n /* edit mode */\n .ewrap { position: relative; }\n .etools { display: none; position: absolute; top: -12px; right: 8px; gap: 2px; z-index: 4;\n background: #fff; border: 1px solid var(--line); border-radius: 6px; padding: 1px 3px;\n box-shadow: 0 2px 8px rgba(0,0,0,.15);\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif; }\n .ewrap:hover > .etools, .ewrap:focus-within > .etools { display: flex; }\n .etools button { font-size: 12px; line-height: 1.4; padding: 1px 6px; border-radius: 4px; }\n .etools button:hover, .etools button:focus-visible { background: #eef0ee; }\n .eedit input { width: 100%; font: inherit; font-size: 13px; padding: 4px 6px; margin-top: 4px;\n border: 1px solid var(--accent); border-radius: 6px; }\n .tokpanel { width: 196px; flex-shrink: 0; background: var(--panel); border: 1px solid var(--line);\n border-radius: 10px; padding: 10px 12px; margin-right: 18px; font-size: 12px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif;\n color: var(--ink); }\n .tokpanel h3 { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim);\n margin-bottom: 6px; }\n .tokpanel label { display: flex; align-items: center; justify-content: space-between; gap: 8px;\n margin: 6px 0; }\n .tokpanel input[type=color] { width: 44px; height: 24px; padding: 0; border: 1px solid var(--line);\n border-radius: 4px; background: none; cursor: pointer; }\n .tokpanel input[type=number], .tokpanel input[type=text] { width: 96px; font: inherit; font-size: 12px;\n padding: 2px 5px; border: 1px solid var(--line); border-radius: 4px; }\n .addblock { border: 1.5px dashed var(--blockline); border-radius: 8px; padding: 8px; width: 100%;\n text-align: center; color: var(--dim); font-size: 12.5px; }\n .addblock:hover, .addblock:focus-visible { border-color: var(--accent); color: var(--accent); }\n .palette { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }\n .palette button { border: 1px solid var(--line); border-radius: 6px; padding: 3px 9px; font-size: 12px;\n background: #fff; }\n .palette button:hover, .palette button:focus-visible { border-color: var(--accent); color: var(--accent); }\n .toast { position: fixed; bottom: 18px; left: 50%; transform: translateX(-50%); z-index: 20;\n display: flex; gap: 10px; align-items: center; background: var(--err); color: #fff;\n border-radius: 8px; padding: 8px 14px; font-size: 13px; max-width: 70vw;\n box-shadow: 0 6px 20px rgba(0,0,0,.3); }\n .toast button { color: #fff; font-size: 14px; }\n</style>\n</head>\n<body>\n<aside id=\"side\"></aside>\n<main>\n <div class=\"bar\" id=\"bar\"></div>\n <div class=\"tabs\" id=\"tabs\"></div>\n <div class=\"stage\" id=\"stage\"></div>\n <div class=\"meta\" id=\"meta\"></div>\n</main>\n<script>\n\"use strict\";\nvar STATIC_INFO = null;\n/*__UXLOOM_STATIC__*/\nvar data = null, comments = [], sel = { screen: null, state: \"default\" }, viewport = \"desktop\";\nvar commentMode = false;\n/* assigned by the edit-mode section below; stripped from static exports */\nvar EDIT = null;\n\nfunction h(tag, cls, text) {\n var el = document.createElement(tag);\n if (cls) el.className = cls;\n if (text != null) el.textContent = text;\n return el;\n}\nfunction screenById(id) { return (data.screens || []).find(function (s) { return s.id === id; }); }\nfunction splitTarget(ref) {\n var i = ref.indexOf(\"#\");\n return i < 0 ? { state: ref } : { state: ref.slice(0, i), screenState: ref.slice(i + 1) };\n}\n/* an \"on\" value is either \"target\" or { target, guard?, roles? } */\nfunction normOn(v) { return typeof v === \"string\" ? { target: v } : (v || {}); }\nfunction pick(screenId, stateId) {\n sel = { screen: screenId, state: stateId || \"default\" };\n render();\n}\nfunction postJson(url, payload) {\n return fetch(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(payload)\n }).then(function (r) {\n if (!r.ok) throw new Error(\"HTTP \" + r.status);\n return r.json();\n });\n}\n\n/* -------- theme: apply project tokens; grayscale when absent -------- */\nfunction applyTheme() {\n var r = document.documentElement.style;\n [\"--accent\", \"--mock-accent\", \"--mock-bg\", \"--block\", \"--mock-text\", \"--mock-muted\",\n \"--mock-radius\", \"--mock-font\"].forEach(function (p) { r.removeProperty(p); });\n var t = data && data.tokens;\n if (!t) return;\n var c = t.colors || {};\n if (c.accent) { r.setProperty(\"--accent\", c.accent); r.setProperty(\"--mock-accent\", c.accent); }\n if (c.bg) r.setProperty(\"--mock-bg\", c.bg);\n if (c.surface) r.setProperty(\"--block\", c.surface);\n if (c.text) r.setProperty(\"--mock-text\", c.text);\n if (c.muted) r.setProperty(\"--mock-muted\", c.muted);\n if (typeof t.radius === \"number\") r.setProperty(\"--mock-radius\", t.radius + \"px\");\n if (t.font) r.setProperty(\"--mock-font\", t.font);\n}\n\n/* ---------- derive blocks when a screen declares no layout ---------- */\nfunction autoBlocks(screen) {\n var blocks = [{ type: \"header\", label: screen.id }];\n if (screen.intent) blocks.push({ type: \"text\", label: screen.intent });\n var hasList = false;\n (screen.components || []).forEach(function (c) {\n var s = (c.semantic || \"\").toLowerCase();\n /* carry interaction intent (R12) through so derived blocks chip it */\n function derived(type, label) {\n return { type: type, label: label, validation: c.validation, motion: c.motion };\n }\n if (s.indexOf(\"button\") === 0) blocks.push(derived(\"button\", c.label && c.label.en || c.semantic));\n else if (s.indexOf(\"input\") === 0 || s.indexOf(\"field\") === 0)\n blocks.push(derived(\"field\", c.label && c.label.en || c.semantic));\n else if (s.indexOf(\"list\") === 0 || s.indexOf(\"table\") === 0) { blocks.push(derived(\"list\", c.semantic)); hasList = true; }\n else if (s.indexOf(\"nav\") === 0) blocks.splice(1, 0, derived(\"nav\", c.semantic));\n else blocks.push(derived(\"card\", c.semantic));\n });\n if (!hasList) blocks.push({ type: \"list\", label: \"Content\", count: 3 });\n return blocks;\n}\n\n/* -------- R12 chips: validation / sort / filter / motion intent ------ */\nfunction metaChip(text, title) {\n var s = h(\"span\", \"srcchip\", text);\n if (title) s.title = title;\n return s;\n}\n/* validation/motion live on components; blocks derived by autoBlocks carry\n them directly, explicit field/button blocks match a component by label */\nfunction blockMeta(b, screen) {\n var v = b.validation, m = b.motion;\n if ((b.type === \"field\" || b.type === \"button\") && screen && screen.components && (!v || !m)) {\n var match = screen.components.find(function (c) {\n return b.label && ((c.label && c.label.en === b.label) || c.semantic === b.label);\n });\n if (match) { if (!v) v = match.validation; if (!m) m = match.motion; }\n }\n return { validation: v, motion: m };\n}\nfunction appendMetaChips(el, b, screen) {\n var meta = blockMeta(b, screen);\n if (meta.validation) {\n if (meta.validation.required) el.appendChild(metaChip(\"required\", meta.validation.message));\n if (meta.validation.pattern)\n el.appendChild(metaChip(\"pattern: \" + meta.validation.pattern, meta.validation.message));\n }\n if (b.type === \"table\" || b.type === \"list\") {\n if (b.sort && b.sort.length) el.appendChild(metaChip(\"sort: \" + b.sort.join(\", \")));\n if (b.filter && b.filter.length) el.appendChild(metaChip(\"filter: \" + b.filter.join(\", \")));\n }\n if (meta.motion === \"decorative\") el.appendChild(metaChip(\"motion: decorative\"));\n}\n\nfunction renderBlock(b, screen) {\n var el, i, j, n = b.count || 3;\n switch (b.type) {\n case \"list\":\n el = h(\"div\", \"kids\");\n for (i = 0; i < n; i++) { var r = h(\"div\", \"row\"); r.appendChild(h(\"div\", \"av\")); r.appendChild(h(\"div\", \"ln\")); el.appendChild(r); }\n break;\n case \"card\":\n el = h(\"div\", \"cards\");\n for (i = 0; i < (b.count || 2); i++) { var c = h(\"div\", \"b\"); c.appendChild(h(\"div\", \"lab\", b.label || \"Card\")); c.appendChild(h(\"div\", \"b-text\")).appendChild(h(\"div\", \"ln\")); el.appendChild(c); }\n break;\n case \"table\":\n el = h(\"div\", \"tbl\");\n var cols = (b.columns && b.columns.length) ? b.columns : null;\n var ncol = cols ? cols.length : 3;\n var hr = h(\"div\", \"tr\");\n for (j = 0; j < ncol; j++) {\n var hd = h(\"div\", \"td\" + (cols ? \" hd\" : \"\"));\n if (cols) hd.textContent = cols[j]; else hd.appendChild(h(\"div\", \"ln\"));\n hr.appendChild(hd);\n }\n el.appendChild(hr);\n for (i = 0; i < n; i++) { var tr = h(\"div\", \"tr\"); for (j = 0; j < ncol; j++) tr.appendChild(h(\"div\", \"td\")).appendChild(h(\"div\", \"ln\")); el.appendChild(tr); }\n break;\n case \"button\": el = h(\"button\", \"b-button\", b.label || \"Action\"); break;\n case \"field\": el = h(\"div\", \"b b-field\"); el.appendChild(h(\"div\", \"lab\", b.label || \"Field\")); el.appendChild(h(\"div\", \"inp\")); break;\n case \"text\":\n el = h(\"div\", \"b b-text\"); if (b.label) el.appendChild(h(\"div\", \"lab\", b.label));\n if (b.copy) { el.appendChild(h(\"div\", \"copy\", b.copy)); }\n else { el.appendChild(h(\"div\", \"ln\")); el.appendChild(h(\"div\", \"ln\")); }\n break;\n case \"image\": el = h(\"div\", \"b b-image\"); el.appendChild(h(\"span\", \"lab\", b.label || \"image\")); break;\n case \"hero\":\n el = h(\"div\", \"b b-hero\");\n if (b.copy) el.appendChild(h(\"span\", \"copy\", b.copy));\n else el.appendChild(h(\"span\", \"lab\", b.label || \"Hero\"));\n break;\n case \"header\": case \"nav\": case \"footer\":\n el = h(\"div\", \"b b-\" + b.type); el.appendChild(h(\"span\", \"lab\", b.label || b.type));\n el.appendChild(h(\"span\", \"pill\")); el.appendChild(h(\"span\", \"pill\")); break;\n case \"form\":\n el = h(\"div\", \"b\"); el.appendChild(h(\"div\", \"lab\", b.label || \"Form\"));\n var kk = h(\"div\", \"kids\"); (b.children || [{ type: \"field\" }, { type: \"field\" }, { type: \"button\", label: \"Submit\" }]).forEach(function (ch) { kk.appendChild(renderBlock(ch, screen)); }); el.appendChild(kk); break;\n default:\n el = h(\"div\", \"b\"); el.appendChild(h(\"div\", \"lab\", b.label || b.type));\n }\n if (b.children && b.type !== \"form\") {\n var kids = h(\"div\", \"kids\");\n b.children.forEach(function (ch) { kids.appendChild(renderBlock(ch, screen)); });\n el.appendChild(kids);\n }\n if (b.source) el.appendChild(h(\"span\", \"srcchip\", b.source));\n appendMetaChips(el, b, screen);\n return el;\n}\n\nfunction renderScreenBody(screen, stateId) {\n var body = h(\"div\", \"screen\");\n body.setAttribute(\"data-ux-screen\", screen.id);\n body.setAttribute(\"data-ux-state\", stateId);\n var blocks = (screen.layout && screen.layout.blocks) || autoBlocks(screen);\n var isError = stateId.indexOf(\"error\") === 0;\n var baseline = stateId === \"default\" || stateId === \"empty\" || stateId === \"loading\";\n /* editing needs an explicit layout: block indexes must map 1:1 to file */\n var editing = EDIT && EDIT.on && !STATIC_INFO && screen.layout && screen.layout.blocks;\n var content = h(\"div\", stateId === \"loading\" ? \"skel\" : \"\");\n content.style.display = \"flex\"; content.style.flexDirection = \"column\"; content.style.gap = \"10px\";\n\n if (stateId === \"empty\") {\n blocks.filter(function (b) { return [\"header\", \"nav\"].indexOf(b.type) >= 0; })\n .forEach(function (b) { content.appendChild(renderBlock(b, screen)); });\n var eb = h(\"div\", \"emptybox\");\n eb.appendChild(h(\"div\", null, \"Nothing here yet\"));\n eb.appendChild(h(\"small\", null, screen.intent || \"Empty state \u2014 first-run guidance goes here\"));\n content.appendChild(eb);\n } else {\n blocks.forEach(function (b, i) {\n var el = renderBlock(b, screen);\n content.appendChild(editing ? EDIT.wrap(el, b, i, screen) : el);\n });\n }\n\n if (isError) {\n body.appendChild(h(\"div\", \"banner\", \"\\u26a0 \" + stateId + \" \u2014 what went wrong and how to fix it\"));\n content.className += \" dimmed\";\n }\n body.appendChild(content);\n if (editing && stateId !== \"empty\") body.appendChild(EDIT.addButton(screen));\n\n if (!baseline && !isError) { // custom states render as overlays over the default\n var ov = h(\"div\", \"overlay\"), mo = h(\"div\", \"modal\");\n mo.appendChild(h(\"h4\", null, stateId));\n mo.appendChild(h(\"div\", \"b-text\")).appendChild(h(\"div\", \"ln\"));\n mo.appendChild(h(\"button\", \"b-button\", \"Confirm\"));\n ov.appendChild(mo); body.appendChild(ov);\n }\n return body;\n}\n\n/* --------------------------- comment mode --------------------------- */\nfunction openCommentsFor(screenId) {\n return comments.filter(function (c) { return !c.resolved && c.screen === screenId; });\n}\n\nfunction attachComments(body, screen) {\n var mine = comments.filter(function (c) {\n return !c.resolved && c.screen === screen.id && c.state === sel.state;\n });\n mine.forEach(function (c, i) {\n var w = h(\"div\", \"pinwrap\");\n w.style.left = c.x + \"%\"; w.style.top = c.y + \"%\";\n var p = h(\"button\", \"pin\", String(i + 1));\n p.setAttribute(\"aria-label\", \"Comment \" + (i + 1) + \": \" + c.text);\n p.onclick = function (e) { e.stopPropagation(); w.classList.toggle(\"open\"); };\n var pop = h(\"div\", \"pinpop\");\n pop.appendChild(h(\"div\", null, c.text));\n var rb = h(\"button\", \"resolve\", \"Resolve\");\n rb.onclick = function (e) {\n e.stopPropagation();\n postJson(\"/comments/resolve\", { id: c.id }).then(function () {\n c.resolved = true; render();\n }).catch(function () {});\n };\n pop.appendChild(rb);\n pop.onclick = function (e) { e.stopPropagation(); };\n w.appendChild(p); w.appendChild(pop);\n body.appendChild(w);\n });\n if (commentMode) body.addEventListener(\"click\", function (e) { placePin(e, body, screen); });\n}\n\nfunction placePin(e, body, screen) {\n var existing = body.querySelector(\".cform\");\n if (existing) { existing.remove(); return; }\n if (e.target.closest && e.target.closest(\".pinwrap\")) return;\n var rect = body.getBoundingClientRect();\n var x = Math.round((e.clientX - rect.left) / rect.width * 1000) / 10;\n var y = Math.round((e.clientY - rect.top) / rect.height * 1000) / 10;\n var form = h(\"div\", \"cform\");\n form.style.left = Math.min(Math.max(x, 2), 68) + \"%\";\n form.style.top = Math.min(Math.max(y, 2), 88) + \"%\";\n var inp = h(\"input\");\n inp.setAttribute(\"type\", \"text\");\n inp.setAttribute(\"placeholder\", \"Leave a comment\\u2026\");\n inp.setAttribute(\"aria-label\", \"Comment text\");\n var save = h(\"button\", \"save\", \"Add\"), cancel = h(\"button\", null, \"Cancel\");\n function submit() {\n var text = inp.value.trim();\n if (!text) return;\n postJson(\"/comments\", { screen: screen.id, state: sel.state, x: x, y: y, text: text })\n .then(function (c) { comments.push(c); render(); })\n .catch(function () { form.remove(); });\n }\n save.onclick = function (ev) { ev.stopPropagation(); submit(); };\n cancel.onclick = function (ev) { ev.stopPropagation(); form.remove(); };\n inp.onkeydown = function (ev) {\n if (ev.key === \"Enter\") submit();\n if (ev.key === \"Escape\") form.remove();\n };\n form.onclick = function (ev) { ev.stopPropagation(); };\n var acts = h(\"div\", \"acts\"); acts.appendChild(cancel); acts.appendChild(save);\n form.appendChild(inp); form.appendChild(acts);\n body.appendChild(form);\n inp.focus();\n}\n\n/* ------------------------------ chrome ------------------------------ */\nfunction chromeFor(vp) {\n var c = h(\"div\", \"chrome\");\n var native = (data.platforms || []).some(function (p) { return p === \"ios\" || p === \"android\"; });\n if (vp === \"mobile\" && native && (data.platforms || []).indexOf(\"web\") < 0 && (data.platforms || []).indexOf(\"mweb\") < 0) {\n c.appendChild(h(\"span\", \"notch\", \"9:41 \\u2014 \" + data.platforms.join(\" / \")));\n } else if (vp === \"mobile\") {\n c.appendChild(h(\"span\", \"notch\", \"9:41 \\u2014 mobile web\"));\n } else {\n c.appendChild(h(\"span\", \"dot\")); c.appendChild(h(\"span\", \"dot\")); c.appendChild(h(\"span\", \"dot\"));\n c.appendChild(h(\"span\", \"url\", (data.name || \"app\") + \".example.com\"));\n }\n return c;\n}\n\n/* ------------------------------ render ------------------------------ */\nfunction render() {\n if (!data) return;\n if (!sel.screen && data.screens && data.screens.length) sel.screen = data.screens[0].id;\n var screen = screenById(sel.screen);\n\n // sidebar\n var side = document.getElementById(\"side\"); side.innerHTML = \"\";\n side.appendChild(h(\"h1\", null, \"UXLoom preview\"));\n side.appendChild(h(\"div\", \"sub\", data.name + \" \\u00b7 \" + (data.platforms || []).join(\", \")));\n side.appendChild(h(\"h2\", null, \"Journeys\"));\n (data.journeys || []).forEach(function (j) {\n var jn = h(\"div\", \"jname\");\n jn.appendChild(document.createTextNode(j.id));\n if (j.platforms && j.platforms.length) jn.appendChild(h(\"small\", \"jplat\", j.platforms.join(\" \\u00b7 \")));\n side.appendChild(jn);\n Object.keys(j.states).forEach(function (stateName) {\n var js = j.states[stateName];\n var b = h(\"button\", \"jstate\" + (screen && js.screen === screen.id ? \" on\" : \"\"));\n b.appendChild(document.createTextNode(stateName + \" \"));\n var sm = h(\"small\", null, \"\\u2192 \" + js.screen + (js.final ? \" \\u2713\" : \"\"));\n b.appendChild(sm);\n b.onclick = function () { pick(js.screen, \"default\"); };\n side.appendChild(b);\n });\n });\n side.appendChild(h(\"h2\", null, \"Screens\"));\n (data.screens || []).forEach(function (s) {\n var b = h(\"button\", \"snav\" + (screen && s.id === screen.id ? \" on\" : \"\"), s.id);\n b.onclick = function () { pick(s.id, \"default\"); };\n side.appendChild(b);\n });\n\n // bar\n var bar = document.getElementById(\"bar\"); bar.innerHTML = \"\";\n [\"desktop\", \"tablet\", \"mobile\"].forEach(function (vp) {\n var b = h(\"button\", \"vp\" + (viewport === vp ? \" on\" : \"\"), vp);\n b.onclick = function () { viewport = vp; render(); };\n bar.appendChild(b);\n });\n bar.appendChild(h(\"div\", \"grow\"));\n if (screen) {\n var req = screen.requiredStates.length;\n var des = screen.requiredStates.filter(function (s) { return screen.designedStates.indexOf(s) >= 0; }).length;\n bar.appendChild(h(\"span\", \"chip\", \"coverage \" + des + \"/\" + req));\n }\n if (!STATIC_INFO && screen) {\n var openCount = openCommentsFor(screen.id).length;\n var cb = h(\"button\", \"cbtn\" + (commentMode ? \" on\" : \"\"));\n cb.appendChild(document.createTextNode(\"\\ud83d\\udcac comment\"));\n if (openCount) cb.appendChild(h(\"span\", \"cnt\", String(openCount)));\n cb.setAttribute(\"aria-pressed\", commentMode ? \"true\" : \"false\");\n cb.title = \"Comment mode: click the mock to leave a pinned note\";\n cb.onclick = function () {\n commentMode = !commentMode;\n if (commentMode && EDIT) EDIT.on = false; /* modes are exclusive */\n render();\n };\n bar.appendChild(cb);\n if (EDIT) bar.appendChild(EDIT.toggleButton());\n }\n if (STATIC_INFO) bar.appendChild(h(\"span\", \"chip\", \"static export \\u00b7 \" + STATIC_INFO.generated));\n else bar.appendChild(h(\"span\", \"chip live\", \"\\u25cf live\"));\n\n // tabs\n var tabs = document.getElementById(\"tabs\"); tabs.innerHTML = \"\";\n if (screen) screen.requiredStates.forEach(function (st) {\n var designed = screen.designedStates.indexOf(st) >= 0;\n var t = h(\"button\", \"tab\" + (sel.state === st ? \" on\" : \"\") + (designed ? \"\" : \" undesigned\"), st + (designed ? \"\" : \" \\u25cb\"));\n t.title = designed ? st : st + \" \u2014 contracted but not designed yet\";\n t.onclick = function () { sel.state = st; render(); };\n tabs.appendChild(t);\n });\n\n // stage\n var stage = document.getElementById(\"stage\"); stage.innerHTML = \"\";\n stage.className = \"stage\" + (commentMode && !STATIC_INFO ? \" commenting\" : \"\");\n if (!screen) { stage.appendChild(h(\"div\", \"err-load\", \"No screens in the project yet \u2014 ask your agent to design some.\")); }\n else {\n if (EDIT && EDIT.on && !STATIC_INFO) stage.appendChild(EDIT.tokenPanel());\n var frame = h(\"div\", \"frame \" + viewport);\n frame.appendChild(chromeFor(viewport));\n var body = renderScreenBody(screen, sel.state);\n if (!STATIC_INFO) attachComments(body, screen);\n frame.appendChild(body);\n stage.appendChild(frame);\n }\n\n // meta: outgoing events for journey states showing this screen + exemptions\n var meta = document.getElementById(\"meta\"); meta.innerHTML = \"\";\n if (screen) {\n var events = [];\n (data.journeys || []).forEach(function (j) {\n Object.keys(j.states).forEach(function (sn) {\n var js = j.states[sn];\n if (js.screen !== screen.id) return;\n Object.keys(js.on || {}).forEach(function (ev) {\n var o = normOn(js.on[ev]);\n if (!o.target) return;\n events.push({ ev: ev, target: o.target, guard: o.guard, roles: o.roles, journey: j.id });\n });\n });\n });\n if (events.length) {\n meta.appendChild(h(\"span\", \"lbl\", \"Events\"));\n events.forEach(function (e) {\n var t = splitTarget(e.target);\n var jj = (data.journeys || []).find(function (x) { return x.id === e.journey; });\n var targetState = jj && jj.states[t.state];\n var b = h(\"button\", \"ev\");\n b.appendChild(document.createTextNode(e.ev + \" \"));\n b.appendChild(h(\"small\", null, \"\\u2192 \" + (targetState ? targetState.screen : t.state) + (t.screenState ? \"#\" + t.screenState : \"\")));\n if (e.guard) b.appendChild(h(\"span\", \"g\", \"[if \" + e.guard + \"]\"));\n if (e.roles && e.roles.length) b.appendChild(h(\"span\", \"g\", \"[\" + e.roles.join(\"|\") + \"]\"));\n b.onclick = function () { if (targetState) pick(targetState.screen, t.screenState || \"default\"); };\n meta.appendChild(b);\n });\n }\n if (screen.data) {\n var fields = Object.keys(screen.data);\n if (fields.length) meta.appendChild(h(\"span\", \"datameta\",\n \"data: \" + fields.map(function (k) { return k + \": \" + screen.data[k]; }).join(\", \")));\n }\n (screen.exemptions || []).forEach(function (ex) {\n meta.appendChild(h(\"span\", \"note\", \"exempt \" + ex.state + \": \" + ex.reason));\n });\n }\n}\n\nfunction boot(p) {\n data = p;\n if (sel.screen && !screenById(sel.screen)) sel = { screen: null, state: \"default\" };\n var hasDesktop = (data.platforms || []).indexOf(\"web\") >= 0;\n if (!hasDesktop && viewport === \"desktop\") viewport = \"mobile\";\n applyTheme();\n render();\n}\n\nfunction load() {\n if (STATIC_INFO) { boot(STATIC_INFO.data); return; }\n Promise.all([\n fetch(\"/project\").then(function (r) { return r.json(); }),\n fetch(\"/comments\").then(function (r) { return r.json(); }).catch(function () { return { comments: [] }; })\n ]).then(function (rs) {\n var p = rs[0];\n if (p && p.error) throw new Error(p.error);\n comments = (rs[1] && rs[1].comments) || [];\n boot(p);\n }).catch(function (e) {\n document.getElementById(\"stage\").innerHTML = \"\";\n document.getElementById(\"stage\").appendChild(h(\"div\", \"err-load\", \"Cannot load project: \" + e.message));\n });\n}\n/*__UXLOOM_EDIT__*/\n/* ------------- structured edit mode (live preview only) ------------- */\nEDIT = (function () {\n var api = { on: false };\n var BLOCK_TYPES = [\"header\", \"nav\", \"hero\", \"text\", \"list\", \"card\", \"form\",\n \"field\", \"button\", \"image\", \"table\", \"footer\", \"custom\"];\n\n function toast(msg) {\n var old = document.querySelector(\".toast\");\n if (old) old.remove();\n var t = h(\"div\", \"toast\");\n t.setAttribute(\"role\", \"alert\");\n t.appendChild(h(\"span\", null, msg));\n var x = h(\"button\", null, \"\\u2715\");\n x.setAttribute(\"aria-label\", \"Dismiss\");\n x.onclick = function () { t.remove(); };\n t.appendChild(x);\n document.body.appendChild(t);\n }\n\n /* POST an op; the file write wakes the SSE watcher, which re-renders\n every viewer \u2014 success needs no local handling at all */\n function postEdit(payload) {\n fetch(\"/edit\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(payload)\n }).then(function (r) {\n return r.json().catch(function () { return {}; }).then(function (j) {\n if (!r.ok) throw new Error(j.error || (\"HTTP \" + r.status));\n });\n }).catch(function (e) { toast(e.message); });\n }\n\n api.toggleButton = function () {\n var b = h(\"button\", \"cbtn\" + (api.on ? \" on\" : \"\"), \"\\u270e edit\");\n b.setAttribute(\"aria-pressed\", api.on ? \"true\" : \"false\");\n b.title = \"Edit mode: tokens, copy, and blocks \\u2014 writes the project file\";\n b.onclick = function () {\n api.on = !api.on;\n if (api.on) commentMode = false; /* modes are exclusive */\n render();\n };\n return b;\n };\n\n /* 3-digit hex \u2192 6-digit (color inputs only accept #rrggbb) */\n function hex6(v, fallback) {\n if (typeof v !== \"string\") return fallback;\n var m = v.match(/^#([0-9a-fA-F]{3})$/);\n if (m) return \"#\" + m[1].charAt(0) + m[1].charAt(0) + m[1].charAt(1) + m[1].charAt(1) +\n m[1].charAt(2) + m[1].charAt(2);\n return /^#[0-9a-fA-F]{6}$/.test(v) ? v : fallback;\n }\n\n api.tokenPanel = function () {\n var p = h(\"div\", \"tokpanel\");\n p.appendChild(h(\"h3\", null, \"Design tokens\"));\n var t = (data && data.tokens) || {}, c = t.colors || {};\n [[\"accent\", \"#2f6b52\"], [\"bg\", \"#ffffff\"], [\"surface\", \"#ffffff\"],\n [\"text\", \"#2a2e2a\"], [\"muted\", \"#6b706b\"]].forEach(function (def) {\n var name = def[0];\n var lab = h(\"label\");\n lab.appendChild(h(\"span\", null, name));\n var inp = h(\"input\");\n inp.setAttribute(\"type\", \"color\");\n inp.value = hex6(c[name], def[1]);\n inp.setAttribute(\"aria-label\", \"Token color \" + name);\n inp.onchange = function () {\n postEdit({ op: \"set-token\", path: \"colors.\" + name, value: inp.value });\n };\n lab.appendChild(inp);\n p.appendChild(lab);\n });\n var rl = h(\"label\");\n rl.appendChild(h(\"span\", null, \"radius\"));\n var ri = h(\"input\");\n ri.setAttribute(\"type\", \"number\");\n ri.setAttribute(\"min\", \"0\"); ri.setAttribute(\"max\", \"32\"); ri.setAttribute(\"step\", \"1\");\n ri.value = typeof t.radius === \"number\" ? String(t.radius) : \"8\";\n ri.setAttribute(\"aria-label\", \"Token radius in px\");\n ri.onchange = function () {\n var n = Number(ri.value);\n if (isFinite(n)) postEdit({ op: \"set-token\", path: \"radius\", value: n });\n };\n rl.appendChild(ri); p.appendChild(rl);\n var fl = h(\"label\");\n fl.appendChild(h(\"span\", null, \"font\"));\n var fi = h(\"input\");\n fi.setAttribute(\"type\", \"text\");\n fi.value = t.font || \"\";\n fi.setAttribute(\"placeholder\", \"Inter, sans-serif\");\n fi.setAttribute(\"aria-label\", \"Token font stack\");\n fi.onchange = function () {\n if (fi.value.trim()) postEdit({ op: \"set-token\", path: \"font\", value: fi.value.trim() });\n };\n fl.appendChild(fi); p.appendChild(fl);\n return p;\n };\n\n function toolButton(txt, label, fn) {\n var x = h(\"button\", null, txt);\n x.setAttribute(\"aria-label\", label);\n x.title = label;\n x.onclick = function (e) { e.stopPropagation(); fn(); };\n return x;\n }\n\n function inlineEdit(w, b, i, screen) {\n var old = w.querySelector(\".eedit\");\n if (old) { old.remove(); return; }\n var isCopy = b.type === \"text\" || b.type === \"hero\";\n var box = h(\"div\", \"eedit\");\n var inp = h(\"input\");\n inp.setAttribute(\"type\", \"text\");\n inp.value = isCopy ? (b.copy || \"\") : (b.label || \"\");\n inp.setAttribute(\"aria-label\", (isCopy ? \"Copy\" : \"Label\") + \" for \" + b.type + \" block \" + (i + 1) +\n \" \\u2014 Enter saves, Escape cancels\");\n inp.onkeydown = function (ev) {\n if (ev.key === \"Enter\") {\n if (isCopy) postEdit({ op: \"set-copy\", screen: screen.id, blockIndex: i, copy: inp.value });\n else postEdit({ op: \"set-label\", screen: screen.id, blockIndex: i, label: inp.value });\n box.remove();\n }\n if (ev.key === \"Escape\") box.remove();\n };\n box.appendChild(inp);\n w.appendChild(box);\n inp.focus();\n inp.select();\n }\n\n /* hover/focus toolbar on each top-level block of an explicit layout */\n api.wrap = function (el, b, i, screen) {\n var total = screen.layout.blocks.length;\n var w = h(\"div\", \"ewrap\");\n w.appendChild(el);\n var tools = h(\"div\", \"etools\");\n if (i > 0) tools.appendChild(toolButton(\"\\u2191\", \"Move \" + b.type + \" block up\", function () {\n postEdit({ op: \"move-block\", screen: screen.id, from: i, to: i - 1 });\n }));\n if (i < total - 1) tools.appendChild(toolButton(\"\\u2193\", \"Move \" + b.type + \" block down\", function () {\n postEdit({ op: \"move-block\", screen: screen.id, from: i, to: i + 1 });\n }));\n if ([\"text\", \"hero\", \"button\", \"field\"].indexOf(b.type) >= 0) {\n var what = b.type === \"text\" || b.type === \"hero\" ? \"copy\" : \"label\";\n tools.appendChild(toolButton(\"\\u270e\", \"Edit \" + b.type + \" \" + what, function () {\n inlineEdit(w, b, i, screen);\n }));\n }\n tools.appendChild(toolButton(\"\\u2715\", \"Remove \" + b.type + \" block\", function () {\n if (window.confirm(\"Remove this \" + b.type + \" block?\"))\n postEdit({ op: \"remove-block\", screen: screen.id, blockIndex: i });\n }));\n w.appendChild(tools);\n return w;\n };\n\n api.addButton = function (screen) {\n var wrap = h(\"div\");\n var pal = null;\n var btn = h(\"button\", \"addblock\", \"+ add block\");\n btn.setAttribute(\"aria-expanded\", \"false\");\n btn.onclick = function () {\n if (pal) { pal.remove(); pal = null; btn.setAttribute(\"aria-expanded\", \"false\"); return; }\n pal = h(\"div\", \"palette\");\n pal.setAttribute(\"role\", \"menu\");\n BLOCK_TYPES.forEach(function (t) {\n var tb = h(\"button\", null, t);\n tb.setAttribute(\"role\", \"menuitem\");\n tb.onclick = function () {\n postEdit({ op: \"add-block\", screen: screen.id, index: screen.layout.blocks.length, block: { type: t } });\n pal.remove(); pal = null;\n btn.setAttribute(\"aria-expanded\", \"false\");\n };\n pal.appendChild(tb);\n });\n wrap.appendChild(pal);\n btn.setAttribute(\"aria-expanded\", \"true\");\n pal.firstChild.focus();\n };\n wrap.appendChild(btn);\n return wrap;\n };\n\n return api;\n})();\n/*__UXLOOM_EDIT_END__*/\n/*__UXLOOM_LIVE__*/\nnew EventSource(\"/events\").onmessage = function () { load(); };\n/*__UXLOOM_LIVE_END__*/\nload();\n</script>\n</body>\n</html>";
export declare const PREVIEW_TEMPLATE = "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>UXLoom preview</title>\n<style>\n :root {\n --bg: #eceeec; --panel: #f8f9f8; --ink: #2a2e2a; --dim: #6b706b;\n --line: #d2d6d2; --block: #ffffff; --blockline: #c4c9c4;\n --accent: #2f6b52; --warn: #b45309; --err: #b04338;\n }\n * { margin: 0; padding: 0; box-sizing: border-box; }\n body { font: 14px/1.5 -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif;\n background: var(--bg); color: var(--ink); height: 100vh; display: flex; }\n button { font: inherit; cursor: pointer; background: none; border: none; color: inherit; }\n\n /* sidebar */\n aside { width: 250px; flex-shrink: 0; background: var(--panel); border-right: 1px solid var(--line);\n overflow-y: auto; padding: 14px; }\n aside h1 { font-size: 15px; margin-bottom: 2px; }\n aside .sub { color: var(--dim); font-size: 12px; margin-bottom: 14px; }\n aside h2 { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim);\n margin: 14px 0 6px; }\n .jname { font-weight: 600; font-size: 13px; margin-top: 8px; }\n .jplat { color: var(--dim); font-weight: 400; font-size: 11px; margin-left: 5px; }\n .jstate, .snav { display: block; width: 100%; text-align: left; padding: 3px 8px; border-radius: 6px;\n font-size: 13px; color: var(--ink); }\n .jstate:hover, .snav:hover { background: #e6e9e6; }\n .jstate.on, .snav.on { background: var(--accent); color: #fff; }\n .jstate small { color: inherit; opacity: .65; }\n .final-flag { opacity: .6; font-size: 11px; }\n\n /* main */\n main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }\n .bar { display: flex; align-items: center; gap: 8px; padding: 10px 16px;\n border-bottom: 1px solid var(--line); background: var(--panel); flex-wrap: wrap; }\n .bar .grow { flex: 1; }\n .chip { font-size: 12px; color: var(--dim); border: 1px solid var(--line); border-radius: 999px;\n padding: 2px 10px; }\n .chip.live { border-color: var(--accent); color: var(--accent); }\n .vp, .tab { padding: 4px 10px; border-radius: 6px; font-size: 13px; border: 1px solid transparent; }\n .vp.on { background: var(--ink); color: #fff; }\n .tab { border-color: var(--line); }\n .tab.on { background: var(--accent); color: #fff; border-color: var(--accent); }\n .tab.undesigned { border-style: dashed; color: var(--dim); }\n .tabs { display: flex; gap: 6px; padding: 10px 16px 0; flex-wrap: wrap; }\n\n .stage { flex: 1; overflow: auto; display: flex; align-items: flex-start; justify-content: center;\n padding: 26px; }\n .stage.commenting .screen { cursor: crosshair; }\n .frame { background: #fff; border: 1px solid var(--line); border-radius: 12px;\n box-shadow: 0 8px 30px rgba(0,0,0,.08); overflow: hidden; flex-shrink: 0; }\n .frame.desktop { width: 960px; } .frame.tablet { width: 640px; } .frame.mobile { width: 390px; }\n .chrome { display: flex; align-items: center; gap: 6px; padding: 8px 12px; background: #eef0ee;\n border-bottom: 1px solid var(--line); font-size: 11px; color: var(--dim); }\n .dot { width: 9px; height: 9px; border-radius: 50%; background: #cfd3cf; }\n .chrome .url { flex: 1; background: #fff; border: 1px solid var(--line); border-radius: 6px;\n padding: 2px 10px; text-align: center; }\n .notch { flex: 1; text-align: center; }\n .screen { position: relative; padding: 14px; min-height: 420px; display: flex;\n flex-direction: column; gap: 10px;\n background: var(--mock-bg, transparent);\n color: var(--mock-text, var(--ink));\n font-family: var(--mock-font, inherit); }\n\n /* wireframe blocks \u2014 themed via --mock-* custom properties when the\n project declares tokens; grayscale defaults otherwise */\n .b { border: 1.5px solid var(--blockline); border-radius: var(--mock-radius, 8px);\n background: var(--block); padding: 10px 12px; position: relative; }\n .b .lab { font-size: 12px; color: var(--mock-muted, var(--dim)); }\n .b-header, .b-nav, .b-footer { background: #f1f3f1; display: flex; gap: 10px; align-items: center; }\n .b-nav .pill, .b-header .pill { width: 54px; height: 8px; border-radius: 4px; background: var(--blockline); }\n .b-hero { min-height: 90px; display: flex; align-items: center; justify-content: center; }\n .b-hero .copy { font-size: 17px; font-weight: 600; text-align: center; }\n .b-text .ln { height: 8px; border-radius: 4px; background: #e0e3e0; margin: 7px 0; }\n .b-text .ln:last-child { width: 60%; }\n .copy { font-size: 13px; white-space: pre-wrap; }\n .b-image { min-height: 80px;\n background: repeating-linear-gradient(45deg, #f4f5f4, #f4f5f4 8px, #e8eae8 8px, #e8eae8 9px);\n display: flex; align-items: center; justify-content: center; }\n .b-button { display: inline-block; background: var(--mock-accent, var(--ink)); color: #fff;\n border-radius: var(--mock-radius, 8px);\n padding: 9px 18px; font-size: 13px; align-self: flex-start; border: none; }\n .b-field { background: var(--block); } .b-field .inp { height: 30px; border: 1.5px solid var(--blockline);\n border-radius: 6px; margin-top: 5px; background: #fdfdfd; }\n .row { display: flex; gap: 10px; align-items: center; border: 1.5px solid var(--blockline);\n border-radius: var(--mock-radius, 8px); padding: 9px 12px; background: var(--block); }\n .row .av { width: 26px; height: 26px; border-radius: 50%; background: #e0e3e0; flex-shrink: 0; }\n .row .ln { height: 8px; border-radius: 4px; background: #e0e3e0; flex: 1; }\n .cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 10px; }\n .tbl { border: 1.5px solid var(--blockline); border-radius: var(--mock-radius, 8px);\n overflow: hidden; background: var(--block); }\n .tbl .tr { display: flex; border-top: 1px solid #e4e7e4; }\n .tbl .tr:first-child { border-top: none; background: #f1f3f1; }\n .tbl .td { flex: 1; padding: 8px; } .tbl .td .ln { height: 8px; border-radius: 4px; background: #e0e3e0; }\n .tbl .td.hd { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: .04em;\n color: var(--mock-muted, var(--dim)); }\n .srcchip { display: inline-block; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;\n font-size: 10px; color: var(--mock-muted, var(--dim)); border: 1px solid var(--blockline);\n border-radius: 4px; padding: 0 5px; margin-top: 5px; align-self: flex-start; }\n .srcchip + .srcchip { margin-left: 4px; }\n .kids { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }\n\n /* state treatments */\n .skel .b, .skel .row, .skel .tbl { border-color: #e4e7e4; }\n .skel .ln, .skel .pill, .skel .av, .skel .inp, .skel .b-image, .skel .b-button, .skel .copy {\n background: linear-gradient(90deg, #ececec 25%, #f7f7f7 50%, #ececec 75%);\n background-size: 200% 100%; animation: shimmer 1.4s infinite; color: transparent; border-color: transparent; }\n @keyframes shimmer { to { background-position: -200% 0; } }\n @media (prefers-reduced-motion: reduce) { .skel * { animation: none !important; } }\n .banner { border: 1.5px solid var(--err); background: #fdf3f2; color: var(--err);\n border-radius: 8px; padding: 10px 12px; font-size: 13px; }\n .dimmed { opacity: .35; pointer-events: none; }\n .emptybox { border: 2px dashed var(--blockline); border-radius: 10px; padding: 34px 16px;\n text-align: center; color: var(--mock-muted, var(--dim)); }\n .overlay { position: absolute; inset: 0; background: rgba(42,46,42,.35); display: flex;\n align-items: center; justify-content: center; border-radius: 0 0 10px 10px; }\n .modal { background: var(--block); border-radius: var(--mock-radius, 12px); padding: 18px;\n width: min(85%, 340px); box-shadow: 0 12px 40px rgba(0,0,0,.25); }\n .modal h4 { font-size: 13px; margin-bottom: 8px; }\n\n /* events + meta */\n .meta { border-top: 1px solid var(--line); background: var(--panel); padding: 10px 16px;\n display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }\n .meta .lbl { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim); }\n .ev { border: 1px solid var(--accent); color: var(--accent); border-radius: 999px;\n padding: 3px 12px; font-size: 12.5px; }\n .ev:hover { background: var(--accent); color: #fff; }\n .ev small { opacity: .7; }\n .ev .g { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10.5px;\n opacity: .85; margin-left: 4px; }\n .note { font-size: 12px; color: var(--warn); }\n .datameta { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px;\n color: var(--dim); }\n .err-load { margin: auto; color: var(--err); }\n\n /* rationale (the \"why\" evidence panel, R20) */\n .whypanel { width: 340px; flex-shrink: 0; background: var(--panel); border-left: 1px solid var(--line);\n overflow-y: auto; padding: 14px 16px; outline: none; }\n .whytop { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }\n .whytitle { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim); }\n .whyclose { color: var(--dim); font-size: 14px; padding: 2px 6px; border-radius: 6px; }\n .whyclose:hover, .whyclose:focus-visible { background: #e6e9e6; color: var(--ink); }\n .rat-scope { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px;\n color: var(--dim); margin-bottom: 2px; }\n .rat-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }\n .rat-decision { font-size: 14px; font-weight: 600; }\n .rat-conf { display: inline-block; font-size: 10.5px; border-radius: 999px; padding: 1px 9px;\n border: 1px solid var(--line); color: var(--dim); white-space: nowrap; }\n .rat-conf.medium { border-color: var(--accent); color: var(--accent); }\n .rat-conf.high { background: var(--accent); border-color: var(--accent); color: #fff; }\n .rat-reason { font-size: 12.5px; margin: 6px 0 10px; }\n .rat-alts { width: 100%; border-collapse: collapse; font-size: 11.5px; margin: 8px 0 10px; }\n .rat-alts caption { text-align: left; font-size: 10.5px; text-transform: uppercase;\n letter-spacing: .05em; color: var(--dim); margin-bottom: 4px; }\n .rat-alts th, .rat-alts td { border: 1px solid var(--line); padding: 4px 6px; text-align: left;\n vertical-align: top; }\n .rat-alts th { background: #f1f3f1; font-size: 10px; text-transform: uppercase;\n letter-spacing: .05em; color: var(--dim); }\n .rat-opt { font-weight: 600; }\n .rat-pros, .rat-cons { list-style: disc; padding-left: 14px; margin: 0; }\n .rat-pros li { color: #2e7d4f; }\n .rat-cons li { color: var(--err); }\n .rat-src { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-top: 4px; }\n .rat-lbl { font-size: 10.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--dim); }\n .rat-src a { color: var(--accent); font-size: 12px; }\n .whydetails { border-top: 1px solid var(--line); margin-top: 12px; padding-top: 10px; }\n .whydetails summary { cursor: pointer; font-size: 12px; font-weight: 600; margin-bottom: 6px; }\n .whymark { margin-left: 5px; font-size: 11px; opacity: .55; }\n\n /* comment mode */\n .cbtn { border: 1px solid var(--line); border-radius: 999px; padding: 3px 12px; font-size: 12.5px; }\n .cbtn.on { background: var(--accent); color: #fff; border-color: var(--accent); }\n .cbtn .cnt { display: inline-block; margin-left: 6px; min-width: 17px; text-align: center;\n border-radius: 999px; background: var(--accent); color: #fff; font-size: 11px; padding: 0 4px; }\n .cbtn.on .cnt { background: #fff; color: var(--accent); }\n .pinwrap { position: absolute; transform: translate(-50%, -50%); z-index: 5; }\n .pin { width: 20px; height: 20px; border-radius: 50%; background: var(--accent); color: #fff;\n font-size: 11px; line-height: 20px; text-align: center; padding: 0;\n box-shadow: 0 2px 6px rgba(0,0,0,.35); }\n .pinpop { display: none; position: absolute; top: 24px; left: -4px; background: #fff;\n color: var(--ink); border: 1px solid var(--line); border-radius: 8px; padding: 8px 10px;\n width: 210px; font-size: 12.5px; box-shadow: 0 8px 24px rgba(0,0,0,.2); z-index: 6;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif; }\n .pinwrap:hover .pinpop, .pinwrap.open .pinpop, .pinwrap:focus-within .pinpop { display: block; }\n .pinpop .resolve { margin-top: 6px; border: 1px solid var(--accent); color: var(--accent);\n border-radius: 6px; padding: 2px 10px; font-size: 12px; }\n .pinpop .resolve:hover { background: var(--accent); color: #fff; }\n .cform { position: absolute; z-index: 7; background: #fff; border: 1px solid var(--line);\n border-radius: 8px; padding: 8px; box-shadow: 0 8px 24px rgba(0,0,0,.25); width: 230px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif; }\n .cform input { width: 100%; font: inherit; font-size: 12.5px; padding: 4px 6px;\n border: 1px solid var(--line); border-radius: 6px; }\n .cform .acts { display: flex; gap: 6px; margin-top: 6px; justify-content: flex-end; }\n .cform .acts button { font-size: 12px; border: 1px solid var(--line); border-radius: 6px; padding: 2px 10px; }\n .cform .acts .save { background: var(--accent); color: #fff; border-color: var(--accent); }\n\n /* edit mode */\n .ewrap { position: relative; }\n .etools { display: none; position: absolute; top: -12px; right: 8px; gap: 2px; z-index: 4;\n background: #fff; border: 1px solid var(--line); border-radius: 6px; padding: 1px 3px;\n box-shadow: 0 2px 8px rgba(0,0,0,.15);\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif; }\n .ewrap:hover > .etools, .ewrap:focus-within > .etools { display: flex; }\n .etools button { font-size: 12px; line-height: 1.4; padding: 1px 6px; border-radius: 4px; }\n .etools button:hover, .etools button:focus-visible { background: #eef0ee; }\n .eedit input { width: 100%; font: inherit; font-size: 13px; padding: 4px 6px; margin-top: 4px;\n border: 1px solid var(--accent); border-radius: 6px; }\n .tokpanel { width: 196px; flex-shrink: 0; background: var(--panel); border: 1px solid var(--line);\n border-radius: 10px; padding: 10px 12px; margin-right: 18px; font-size: 12px;\n font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", system-ui, sans-serif;\n color: var(--ink); }\n .tokpanel h3 { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim);\n margin-bottom: 6px; }\n .tokpanel label { display: flex; align-items: center; justify-content: space-between; gap: 8px;\n margin: 6px 0; }\n .tokpanel input[type=color] { width: 44px; height: 24px; padding: 0; border: 1px solid var(--line);\n border-radius: 4px; background: none; cursor: pointer; }\n .tokpanel input[type=number], .tokpanel input[type=text] { width: 96px; font: inherit; font-size: 12px;\n padding: 2px 5px; border: 1px solid var(--line); border-radius: 4px; }\n .addblock { border: 1.5px dashed var(--blockline); border-radius: 8px; padding: 8px; width: 100%;\n text-align: center; color: var(--dim); font-size: 12.5px; }\n .addblock:hover, .addblock:focus-visible { border-color: var(--accent); color: var(--accent); }\n .palette { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }\n .palette button { border: 1px solid var(--line); border-radius: 6px; padding: 3px 9px; font-size: 12px;\n background: #fff; }\n .palette button:hover, .palette button:focus-visible { border-color: var(--accent); color: var(--accent); }\n .toast { position: fixed; bottom: 18px; left: 50%; transform: translateX(-50%); z-index: 20;\n display: flex; gap: 10px; align-items: center; background: var(--err); color: #fff;\n border-radius: 8px; padding: 8px 14px; font-size: 13px; max-width: 70vw;\n box-shadow: 0 6px 20px rgba(0,0,0,.3); }\n .toast button { color: #fff; font-size: 14px; }\n</style>\n</head>\n<body>\n<aside id=\"side\"></aside>\n<main>\n <div class=\"bar\" id=\"bar\"></div>\n <div class=\"tabs\" id=\"tabs\"></div>\n <div class=\"stage\" id=\"stage\"></div>\n <div class=\"meta\" id=\"meta\"></div>\n</main>\n<div id=\"why\" class=\"whypanel\" role=\"complementary\" aria-label=\"Design rationale\" tabindex=\"-1\" hidden></div>\n<script>\n\"use strict\";\nvar STATIC_INFO = null;\n/*__UXLOOM_STATIC__*/\nvar data = null, comments = [], sel = { screen: null, state: \"default\" }, viewport = \"desktop\";\nvar commentMode = false;\nvar whyOpen = false;\n/* assigned by the edit-mode section below; stripped from static exports */\nvar EDIT = null;\n\nfunction h(tag, cls, text) {\n var el = document.createElement(tag);\n if (cls) el.className = cls;\n if (text != null) el.textContent = text;\n return el;\n}\nfunction screenById(id) { return (data.screens || []).find(function (s) { return s.id === id; }); }\nfunction splitTarget(ref) {\n var i = ref.indexOf(\"#\");\n return i < 0 ? { state: ref } : { state: ref.slice(0, i), screenState: ref.slice(i + 1) };\n}\n/* an \"on\" value is either \"target\" or { target, guard?, roles? } */\nfunction normOn(v) { return typeof v === \"string\" ? { target: v } : (v || {}); }\nfunction pick(screenId, stateId) {\n sel = { screen: screenId, state: stateId || \"default\" };\n render();\n}\nfunction postJson(url, payload) {\n return fetch(url, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(payload)\n }).then(function (r) {\n if (!r.ok) throw new Error(\"HTTP \" + r.status);\n return r.json();\n });\n}\n\n/* -------- theme: apply project tokens; grayscale when absent -------- */\nfunction applyTheme() {\n var r = document.documentElement.style;\n [\"--accent\", \"--mock-accent\", \"--mock-bg\", \"--block\", \"--mock-text\", \"--mock-muted\",\n \"--mock-radius\", \"--mock-font\"].forEach(function (p) { r.removeProperty(p); });\n var t = data && data.tokens;\n if (!t) return;\n var c = t.colors || {};\n if (c.accent) { r.setProperty(\"--accent\", c.accent); r.setProperty(\"--mock-accent\", c.accent); }\n if (c.bg) r.setProperty(\"--mock-bg\", c.bg);\n if (c.surface) r.setProperty(\"--block\", c.surface);\n if (c.text) r.setProperty(\"--mock-text\", c.text);\n if (c.muted) r.setProperty(\"--mock-muted\", c.muted);\n if (typeof t.radius === \"number\") r.setProperty(\"--mock-radius\", t.radius + \"px\");\n if (t.font) r.setProperty(\"--mock-font\", t.font);\n}\n\n/* ---------- derive blocks when a screen declares no layout ---------- */\nfunction autoBlocks(screen) {\n var blocks = [{ type: \"header\", label: screen.id }];\n if (screen.intent) blocks.push({ type: \"text\", label: screen.intent });\n var hasList = false;\n (screen.components || []).forEach(function (c) {\n var s = (c.semantic || \"\").toLowerCase();\n /* carry interaction intent (R12) through so derived blocks chip it */\n function derived(type, label) {\n return { type: type, label: label, validation: c.validation, motion: c.motion };\n }\n if (s.indexOf(\"button\") === 0) blocks.push(derived(\"button\", c.label && c.label.en || c.semantic));\n else if (s.indexOf(\"input\") === 0 || s.indexOf(\"field\") === 0)\n blocks.push(derived(\"field\", c.label && c.label.en || c.semantic));\n else if (s.indexOf(\"list\") === 0 || s.indexOf(\"table\") === 0) { blocks.push(derived(\"list\", c.semantic)); hasList = true; }\n else if (s.indexOf(\"nav\") === 0) blocks.splice(1, 0, derived(\"nav\", c.semantic));\n else blocks.push(derived(\"card\", c.semantic));\n });\n if (!hasList) blocks.push({ type: \"list\", label: \"Content\", count: 3 });\n return blocks;\n}\n\n/* -------- R12 chips: validation / sort / filter / motion intent ------ */\nfunction metaChip(text, title) {\n var s = h(\"span\", \"srcchip\", text);\n if (title) s.title = title;\n return s;\n}\n/* validation/motion live on components; blocks derived by autoBlocks carry\n them directly, explicit field/button blocks match a component by label */\nfunction blockMeta(b, screen) {\n var v = b.validation, m = b.motion;\n if ((b.type === \"field\" || b.type === \"button\") && screen && screen.components && (!v || !m)) {\n var match = screen.components.find(function (c) {\n return b.label && ((c.label && c.label.en === b.label) || c.semantic === b.label);\n });\n if (match) { if (!v) v = match.validation; if (!m) m = match.motion; }\n }\n return { validation: v, motion: m };\n}\nfunction appendMetaChips(el, b, screen) {\n var meta = blockMeta(b, screen);\n if (meta.validation) {\n if (meta.validation.required) el.appendChild(metaChip(\"required\", meta.validation.message));\n if (meta.validation.pattern)\n el.appendChild(metaChip(\"pattern: \" + meta.validation.pattern, meta.validation.message));\n }\n if (b.type === \"table\" || b.type === \"list\") {\n if (b.sort && b.sort.length) el.appendChild(metaChip(\"sort: \" + b.sort.join(\", \")));\n if (b.filter && b.filter.length) el.appendChild(metaChip(\"filter: \" + b.filter.join(\", \")));\n }\n if (meta.motion === \"decorative\") el.appendChild(metaChip(\"motion: decorative\"));\n}\n\nfunction renderBlock(b, screen) {\n var el, i, j, n = b.count || 3;\n switch (b.type) {\n case \"list\":\n el = h(\"div\", \"kids\");\n for (i = 0; i < n; i++) { var r = h(\"div\", \"row\"); r.appendChild(h(\"div\", \"av\")); r.appendChild(h(\"div\", \"ln\")); el.appendChild(r); }\n break;\n case \"card\":\n el = h(\"div\", \"cards\");\n for (i = 0; i < (b.count || 2); i++) { var c = h(\"div\", \"b\"); c.appendChild(h(\"div\", \"lab\", b.label || \"Card\")); c.appendChild(h(\"div\", \"b-text\")).appendChild(h(\"div\", \"ln\")); el.appendChild(c); }\n break;\n case \"table\":\n el = h(\"div\", \"tbl\");\n var cols = (b.columns && b.columns.length) ? b.columns : null;\n var ncol = cols ? cols.length : 3;\n var hr = h(\"div\", \"tr\");\n for (j = 0; j < ncol; j++) {\n var hd = h(\"div\", \"td\" + (cols ? \" hd\" : \"\"));\n if (cols) hd.textContent = cols[j]; else hd.appendChild(h(\"div\", \"ln\"));\n hr.appendChild(hd);\n }\n el.appendChild(hr);\n for (i = 0; i < n; i++) { var tr = h(\"div\", \"tr\"); for (j = 0; j < ncol; j++) tr.appendChild(h(\"div\", \"td\")).appendChild(h(\"div\", \"ln\")); el.appendChild(tr); }\n break;\n case \"button\": el = h(\"button\", \"b-button\", b.label || \"Action\"); break;\n case \"field\": el = h(\"div\", \"b b-field\"); el.appendChild(h(\"div\", \"lab\", b.label || \"Field\")); el.appendChild(h(\"div\", \"inp\")); break;\n case \"text\":\n el = h(\"div\", \"b b-text\"); if (b.label) el.appendChild(h(\"div\", \"lab\", b.label));\n if (b.copy) { el.appendChild(h(\"div\", \"copy\", b.copy)); }\n else { el.appendChild(h(\"div\", \"ln\")); el.appendChild(h(\"div\", \"ln\")); }\n break;\n case \"image\": el = h(\"div\", \"b b-image\"); el.appendChild(h(\"span\", \"lab\", b.label || \"image\")); break;\n case \"hero\":\n el = h(\"div\", \"b b-hero\");\n if (b.copy) el.appendChild(h(\"span\", \"copy\", b.copy));\n else el.appendChild(h(\"span\", \"lab\", b.label || \"Hero\"));\n break;\n case \"header\": case \"nav\": case \"footer\":\n el = h(\"div\", \"b b-\" + b.type); el.appendChild(h(\"span\", \"lab\", b.label || b.type));\n el.appendChild(h(\"span\", \"pill\")); el.appendChild(h(\"span\", \"pill\")); break;\n case \"form\":\n el = h(\"div\", \"b\"); el.appendChild(h(\"div\", \"lab\", b.label || \"Form\"));\n var kk = h(\"div\", \"kids\"); (b.children || [{ type: \"field\" }, { type: \"field\" }, { type: \"button\", label: \"Submit\" }]).forEach(function (ch) { kk.appendChild(renderBlock(ch, screen)); }); el.appendChild(kk); break;\n default:\n el = h(\"div\", \"b\"); el.appendChild(h(\"div\", \"lab\", b.label || b.type));\n }\n if (b.children && b.type !== \"form\") {\n var kids = h(\"div\", \"kids\");\n b.children.forEach(function (ch) { kids.appendChild(renderBlock(ch, screen)); });\n el.appendChild(kids);\n }\n if (b.source) el.appendChild(h(\"span\", \"srcchip\", b.source));\n appendMetaChips(el, b, screen);\n return el;\n}\n\nfunction renderScreenBody(screen, stateId) {\n var body = h(\"div\", \"screen\");\n body.setAttribute(\"data-ux-screen\", screen.id);\n body.setAttribute(\"data-ux-state\", stateId);\n var blocks = (screen.layout && screen.layout.blocks) || autoBlocks(screen);\n var isError = stateId.indexOf(\"error\") === 0;\n var baseline = stateId === \"default\" || stateId === \"empty\" || stateId === \"loading\";\n /* editing needs an explicit layout: block indexes must map 1:1 to file */\n var editing = EDIT && EDIT.on && !STATIC_INFO && screen.layout && screen.layout.blocks;\n var content = h(\"div\", stateId === \"loading\" ? \"skel\" : \"\");\n content.style.display = \"flex\"; content.style.flexDirection = \"column\"; content.style.gap = \"10px\";\n\n if (stateId === \"empty\") {\n blocks.filter(function (b) { return [\"header\", \"nav\"].indexOf(b.type) >= 0; })\n .forEach(function (b) { content.appendChild(renderBlock(b, screen)); });\n var eb = h(\"div\", \"emptybox\");\n eb.appendChild(h(\"div\", null, \"Nothing here yet\"));\n eb.appendChild(h(\"small\", null, screen.intent || \"Empty state \u2014 first-run guidance goes here\"));\n content.appendChild(eb);\n } else {\n blocks.forEach(function (b, i) {\n var el = renderBlock(b, screen);\n content.appendChild(editing ? EDIT.wrap(el, b, i, screen) : el);\n });\n }\n\n if (isError) {\n body.appendChild(h(\"div\", \"banner\", \"\\u26a0 \" + stateId + \" \u2014 what went wrong and how to fix it\"));\n content.className += \" dimmed\";\n }\n body.appendChild(content);\n if (editing && stateId !== \"empty\") body.appendChild(EDIT.addButton(screen));\n\n if (!baseline && !isError) { // custom states render as overlays over the default\n var ov = h(\"div\", \"overlay\"), mo = h(\"div\", \"modal\");\n mo.appendChild(h(\"h4\", null, stateId));\n mo.appendChild(h(\"div\", \"b-text\")).appendChild(h(\"div\", \"ln\"));\n mo.appendChild(h(\"button\", \"b-button\", \"Confirm\"));\n ov.appendChild(mo); body.appendChild(ov);\n }\n return body;\n}\n\n/* --------------------------- comment mode --------------------------- */\nfunction openCommentsFor(screenId) {\n return comments.filter(function (c) { return !c.resolved && c.screen === screenId; });\n}\n\nfunction attachComments(body, screen) {\n var mine = comments.filter(function (c) {\n return !c.resolved && c.screen === screen.id && c.state === sel.state;\n });\n mine.forEach(function (c, i) {\n var w = h(\"div\", \"pinwrap\");\n w.style.left = c.x + \"%\"; w.style.top = c.y + \"%\";\n var p = h(\"button\", \"pin\", String(i + 1));\n p.setAttribute(\"aria-label\", \"Comment \" + (i + 1) + \": \" + c.text);\n p.onclick = function (e) { e.stopPropagation(); w.classList.toggle(\"open\"); };\n var pop = h(\"div\", \"pinpop\");\n pop.appendChild(h(\"div\", null, c.text));\n var rb = h(\"button\", \"resolve\", \"Resolve\");\n rb.onclick = function (e) {\n e.stopPropagation();\n postJson(\"/comments/resolve\", { id: c.id }).then(function () {\n c.resolved = true; render();\n }).catch(function () {});\n };\n pop.appendChild(rb);\n pop.onclick = function (e) { e.stopPropagation(); };\n w.appendChild(p); w.appendChild(pop);\n body.appendChild(w);\n });\n if (commentMode) body.addEventListener(\"click\", function (e) { placePin(e, body, screen); });\n}\n\nfunction placePin(e, body, screen) {\n var existing = body.querySelector(\".cform\");\n if (existing) { existing.remove(); return; }\n if (e.target.closest && e.target.closest(\".pinwrap\")) return;\n var rect = body.getBoundingClientRect();\n var x = Math.round((e.clientX - rect.left) / rect.width * 1000) / 10;\n var y = Math.round((e.clientY - rect.top) / rect.height * 1000) / 10;\n var form = h(\"div\", \"cform\");\n form.style.left = Math.min(Math.max(x, 2), 68) + \"%\";\n form.style.top = Math.min(Math.max(y, 2), 88) + \"%\";\n var inp = h(\"input\");\n inp.setAttribute(\"type\", \"text\");\n inp.setAttribute(\"placeholder\", \"Leave a comment\\u2026\");\n inp.setAttribute(\"aria-label\", \"Comment text\");\n var save = h(\"button\", \"save\", \"Add\"), cancel = h(\"button\", null, \"Cancel\");\n function submit() {\n var text = inp.value.trim();\n if (!text) return;\n postJson(\"/comments\", { screen: screen.id, state: sel.state, x: x, y: y, text: text })\n .then(function (c) { comments.push(c); render(); })\n .catch(function () { form.remove(); });\n }\n save.onclick = function (ev) { ev.stopPropagation(); submit(); };\n cancel.onclick = function (ev) { ev.stopPropagation(); form.remove(); };\n inp.onkeydown = function (ev) {\n if (ev.key === \"Enter\") submit();\n if (ev.key === \"Escape\") form.remove();\n };\n form.onclick = function (ev) { ev.stopPropagation(); };\n var acts = h(\"div\", \"acts\"); acts.appendChild(cancel); acts.appendChild(save);\n form.appendChild(inp); form.appendChild(acts);\n body.appendChild(form);\n inp.focus();\n}\n\n/* ------------------------------ chrome ------------------------------ */\nfunction chromeFor(vp) {\n var c = h(\"div\", \"chrome\");\n var native = (data.platforms || []).some(function (p) { return p === \"ios\" || p === \"android\"; });\n if (vp === \"mobile\" && native && (data.platforms || []).indexOf(\"web\") < 0 && (data.platforms || []).indexOf(\"mweb\") < 0) {\n c.appendChild(h(\"span\", \"notch\", \"9:41 \\u2014 \" + data.platforms.join(\" / \")));\n } else if (vp === \"mobile\") {\n c.appendChild(h(\"span\", \"notch\", \"9:41 \\u2014 mobile web\"));\n } else {\n c.appendChild(h(\"span\", \"dot\")); c.appendChild(h(\"span\", \"dot\")); c.appendChild(h(\"span\", \"dot\"));\n c.appendChild(h(\"span\", \"url\", (data.name || \"app\") + \".example.com\"));\n }\n return c;\n}\n\n/* ----------- R20: design rationale \u2014 the \"why\" evidence panel ----------- */\n/* the toggle is visible only when the current screen OR the project carries\n rationale; journeys surface theirs via the panel and a muted meta chip */\nfunction hasWhyContent(screen) {\n return !!((screen && screen.rationale) || (data && data.rationale));\n}\nfunction journeysForScreen(screenId) {\n return (data.journeys || []).filter(function (j) {\n return Object.keys(j.states).some(function (sn) { return j.states[sn].screen === screenId; });\n });\n}\nfunction hostOf(u) {\n try { return new URL(u).hostname || u; } catch (e) { return u; }\n}\n/* one rationale rendered as evidence: decision, confidence, reasoning,\n the alternatives that were argued over, and the sources it leans on.\n Every field is optional \u2014 show what exists, never invent the rest. */\nfunction ratBody(r) {\n var box = h(\"div\", \"rat\");\n var head = h(\"div\", \"rat-head\");\n head.appendChild(h(\"h3\", \"rat-decision\", r.decision || \"(no decision recorded)\"));\n if (r.confidence === \"low\" || r.confidence === \"medium\" || r.confidence === \"high\")\n head.appendChild(h(\"span\", \"rat-conf \" + r.confidence, r.confidence + \" confidence\"));\n box.appendChild(head);\n if (r.reasoning) box.appendChild(h(\"p\", \"rat-reason\", r.reasoning));\n if (r.alternatives && r.alternatives.length) {\n var tbl = h(\"table\", \"rat-alts\");\n tbl.appendChild(h(\"caption\", null, \"Alternatives considered\"));\n var thr = h(\"tr\");\n [\"Option\", \"Pros\", \"Cons\"].forEach(function (t) { thr.appendChild(h(\"th\", null, t)); });\n tbl.appendChild(thr);\n r.alternatives.forEach(function (a) {\n var tr = h(\"tr\");\n tr.appendChild(h(\"td\", \"rat-opt\", a.option || \"\"));\n var ptd = h(\"td\"), pul = h(\"ul\", \"rat-pros\");\n (a.pros || []).forEach(function (p) { pul.appendChild(h(\"li\", null, p)); });\n ptd.appendChild(pul); tr.appendChild(ptd);\n var ctd = h(\"td\"), cul = h(\"ul\", \"rat-cons\");\n (a.cons || []).forEach(function (c) { cul.appendChild(h(\"li\", null, c)); });\n ctd.appendChild(cul); tr.appendChild(ctd);\n tbl.appendChild(tr);\n });\n box.appendChild(tbl);\n }\n if (r.sources && r.sources.length) {\n var src = h(\"div\", \"rat-src\");\n src.appendChild(h(\"span\", \"rat-lbl\", \"Sources\"));\n r.sources.forEach(function (u) {\n var a = h(\"a\", null, hostOf(u));\n a.setAttribute(\"href\", u);\n a.setAttribute(\"target\", \"_blank\");\n a.setAttribute(\"rel\", \"noopener\");\n a.title = u;\n src.appendChild(a);\n });\n box.appendChild(src);\n }\n return box;\n}\nfunction closeWhy() {\n whyOpen = false;\n render();\n var t = document.getElementById(\"whytoggle\");\n if (t) t.focus();\n}\nfunction renderWhy(screen) {\n var panel = document.getElementById(\"why\");\n panel.innerHTML = \"\";\n if (!hasWhyContent(screen)) whyOpen = false;\n panel.hidden = !whyOpen;\n if (!whyOpen) return;\n var top = h(\"div\", \"whytop\");\n top.appendChild(h(\"h2\", \"whytitle\", \"Design rationale\"));\n var x = h(\"button\", \"whyclose\", \"\\u2715\");\n x.setAttribute(\"aria-label\", \"Close rationale panel\");\n x.onclick = function () { closeWhy(); };\n top.appendChild(x);\n panel.appendChild(top);\n if (screen && screen.rationale) {\n panel.appendChild(h(\"div\", \"rat-scope\", screen.id));\n panel.appendChild(ratBody(screen.rationale));\n }\n if (data && data.rationale) {\n var pd = h(\"details\", \"whydetails\");\n pd.appendChild(h(\"summary\", null, \"Product direction\"));\n pd.appendChild(ratBody(data.rationale));\n panel.appendChild(pd);\n }\n if (screen) journeysForScreen(screen.id).forEach(function (j) {\n if (!j.rationale) return;\n var jd = h(\"details\", \"whydetails\");\n jd.appendChild(h(\"summary\", null, \"Flow rationale \\u00b7 \" + j.id));\n jd.appendChild(ratBody(j.rationale));\n panel.appendChild(jd);\n });\n}\n\n/* ------------------------------ render ------------------------------ */\nfunction render() {\n if (!data) return;\n if (!sel.screen && data.screens && data.screens.length) sel.screen = data.screens[0].id;\n var screen = screenById(sel.screen);\n\n // sidebar\n var side = document.getElementById(\"side\"); side.innerHTML = \"\";\n side.appendChild(h(\"h1\", null, \"UXLoom preview\"));\n side.appendChild(h(\"div\", \"sub\", data.name + \" \\u00b7 \" + (data.platforms || []).join(\", \")));\n side.appendChild(h(\"h2\", null, \"Journeys\"));\n (data.journeys || []).forEach(function (j) {\n var jn = h(\"div\", \"jname\");\n jn.appendChild(document.createTextNode(j.id));\n if (j.platforms && j.platforms.length) jn.appendChild(h(\"small\", \"jplat\", j.platforms.join(\" \\u00b7 \")));\n side.appendChild(jn);\n Object.keys(j.states).forEach(function (stateName) {\n var js = j.states[stateName];\n var b = h(\"button\", \"jstate\" + (screen && js.screen === screen.id ? \" on\" : \"\"));\n b.appendChild(document.createTextNode(stateName + \" \"));\n var sm = h(\"small\", null, \"\\u2192 \" + js.screen + (js.final ? \" \\u2713\" : \"\"));\n b.appendChild(sm);\n b.onclick = function () { pick(js.screen, \"default\"); };\n side.appendChild(b);\n });\n });\n side.appendChild(h(\"h2\", null, \"Screens\"));\n (data.screens || []).forEach(function (s) {\n var b = h(\"button\", \"snav\" + (screen && s.id === screen.id ? \" on\" : \"\"));\n b.appendChild(document.createTextNode(s.id));\n if (s.rationale) {\n var wm = h(\"span\", \"whymark\", \"\\u24d8\");\n wm.title = \"has design rationale\";\n b.appendChild(wm);\n }\n b.onclick = function () { pick(s.id, \"default\"); };\n side.appendChild(b);\n });\n\n // bar\n var bar = document.getElementById(\"bar\"); bar.innerHTML = \"\";\n [\"desktop\", \"tablet\", \"mobile\"].forEach(function (vp) {\n var b = h(\"button\", \"vp\" + (viewport === vp ? \" on\" : \"\"), vp);\n b.onclick = function () { viewport = vp; render(); };\n bar.appendChild(b);\n });\n bar.appendChild(h(\"div\", \"grow\"));\n if (screen) {\n var req = screen.requiredStates.length;\n var des = screen.requiredStates.filter(function (s) { return screen.designedStates.indexOf(s) >= 0; }).length;\n bar.appendChild(h(\"span\", \"chip\", \"coverage \" + des + \"/\" + req));\n }\n if (!STATIC_INFO && screen) {\n var openCount = openCommentsFor(screen.id).length;\n var cb = h(\"button\", \"cbtn\" + (commentMode ? \" on\" : \"\"));\n cb.appendChild(document.createTextNode(\"\\ud83d\\udcac comment\"));\n if (openCount) cb.appendChild(h(\"span\", \"cnt\", String(openCount)));\n cb.setAttribute(\"aria-pressed\", commentMode ? \"true\" : \"false\");\n cb.title = \"Comment mode: click the mock to leave a pinned note\";\n cb.onclick = function () {\n commentMode = !commentMode;\n if (commentMode && EDIT) EDIT.on = false; /* modes are exclusive */\n render();\n };\n bar.appendChild(cb);\n if (EDIT) bar.appendChild(EDIT.toggleButton());\n }\n if (hasWhyContent(screen)) {\n var wb = h(\"button\", \"cbtn\" + (whyOpen ? \" on\" : \"\"), \"\\u24d8 why\");\n wb.id = \"whytoggle\";\n wb.setAttribute(\"aria-pressed\", whyOpen ? \"true\" : \"false\");\n wb.setAttribute(\"aria-controls\", \"why\");\n wb.title = \"Why: the design rationale behind this screen\";\n wb.onclick = function () {\n whyOpen = !whyOpen;\n render();\n if (whyOpen) document.getElementById(\"why\").focus();\n };\n bar.appendChild(wb);\n }\n if (STATIC_INFO) bar.appendChild(h(\"span\", \"chip\", \"static export \\u00b7 \" + STATIC_INFO.generated));\n else bar.appendChild(h(\"span\", \"chip live\", \"\\u25cf live\"));\n\n // tabs\n var tabs = document.getElementById(\"tabs\"); tabs.innerHTML = \"\";\n if (screen) screen.requiredStates.forEach(function (st) {\n var designed = screen.designedStates.indexOf(st) >= 0;\n var t = h(\"button\", \"tab\" + (sel.state === st ? \" on\" : \"\") + (designed ? \"\" : \" undesigned\"), st + (designed ? \"\" : \" \\u25cb\"));\n t.title = designed ? st : st + \" \u2014 contracted but not designed yet\";\n t.onclick = function () { sel.state = st; render(); };\n tabs.appendChild(t);\n });\n\n // stage\n var stage = document.getElementById(\"stage\"); stage.innerHTML = \"\";\n stage.className = \"stage\" + (commentMode && !STATIC_INFO ? \" commenting\" : \"\");\n if (!screen) { stage.appendChild(h(\"div\", \"err-load\", \"No screens in the project yet \u2014 ask your agent to design some.\")); }\n else {\n if (EDIT && EDIT.on && !STATIC_INFO) stage.appendChild(EDIT.tokenPanel());\n var frame = h(\"div\", \"frame \" + viewport);\n frame.appendChild(chromeFor(viewport));\n var body = renderScreenBody(screen, sel.state);\n if (!STATIC_INFO) attachComments(body, screen);\n frame.appendChild(body);\n stage.appendChild(frame);\n }\n\n // meta: outgoing events for journey states showing this screen + exemptions\n var meta = document.getElementById(\"meta\"); meta.innerHTML = \"\";\n if (screen) {\n var events = [];\n (data.journeys || []).forEach(function (j) {\n Object.keys(j.states).forEach(function (sn) {\n var js = j.states[sn];\n if (js.screen !== screen.id) return;\n Object.keys(js.on || {}).forEach(function (ev) {\n var o = normOn(js.on[ev]);\n if (!o.target) return;\n events.push({ ev: ev, target: o.target, guard: o.guard, roles: o.roles, journey: j.id });\n });\n });\n });\n if (events.length) {\n meta.appendChild(h(\"span\", \"lbl\", \"Events\"));\n events.forEach(function (e) {\n var t = splitTarget(e.target);\n var jj = (data.journeys || []).find(function (x) { return x.id === e.journey; });\n var targetState = jj && jj.states[t.state];\n var b = h(\"button\", \"ev\");\n b.appendChild(document.createTextNode(e.ev + \" \"));\n b.appendChild(h(\"small\", null, \"\\u2192 \" + (targetState ? targetState.screen : t.state) + (t.screenState ? \"#\" + t.screenState : \"\")));\n if (e.guard) b.appendChild(h(\"span\", \"g\", \"[if \" + e.guard + \"]\"));\n if (e.roles && e.roles.length) b.appendChild(h(\"span\", \"g\", \"[\" + e.roles.join(\"|\") + \"]\"));\n b.onclick = function () { if (targetState) pick(targetState.screen, t.screenState || \"default\"); };\n meta.appendChild(b);\n });\n }\n if (screen.data) {\n var fields = Object.keys(screen.data);\n if (fields.length) meta.appendChild(h(\"span\", \"datameta\",\n \"data: \" + fields.map(function (k) { return k + \": \" + screen.data[k]; }).join(\", \")));\n }\n (screen.exemptions || []).forEach(function (ex) {\n meta.appendChild(h(\"span\", \"note\", \"exempt \" + ex.state + \": \" + ex.reason));\n });\n journeysForScreen(screen.id).forEach(function (j) {\n if (j.rationale && j.rationale.decision)\n meta.appendChild(h(\"span\", \"chip flowchip\", \"flow: \" + j.rationale.decision));\n });\n }\n\n renderWhy(screen);\n}\n\nfunction boot(p) {\n data = p;\n if (sel.screen && !screenById(sel.screen)) sel = { screen: null, state: \"default\" };\n var hasDesktop = (data.platforms || []).indexOf(\"web\") >= 0;\n if (!hasDesktop && viewport === \"desktop\") viewport = \"mobile\";\n applyTheme();\n render();\n}\n\nfunction load() {\n if (STATIC_INFO) { boot(STATIC_INFO.data); return; }\n Promise.all([\n fetch(\"/project\").then(function (r) { return r.json(); }),\n fetch(\"/comments\").then(function (r) { return r.json(); }).catch(function () { return { comments: [] }; })\n ]).then(function (rs) {\n var p = rs[0];\n if (p && p.error) throw new Error(p.error);\n comments = (rs[1] && rs[1].comments) || [];\n boot(p);\n }).catch(function (e) {\n document.getElementById(\"stage\").innerHTML = \"\";\n document.getElementById(\"stage\").appendChild(h(\"div\", \"err-load\", \"Cannot load project: \" + e.message));\n });\n}\ndocument.addEventListener(\"keydown\", function (e) {\n if (e.key === \"Escape\" && whyOpen) closeWhy();\n});\n/*__UXLOOM_EDIT__*/\n/* ------------- structured edit mode (live preview only) ------------- */\nEDIT = (function () {\n var api = { on: false };\n var BLOCK_TYPES = [\"header\", \"nav\", \"hero\", \"text\", \"list\", \"card\", \"form\",\n \"field\", \"button\", \"image\", \"table\", \"footer\", \"custom\"];\n\n function toast(msg) {\n var old = document.querySelector(\".toast\");\n if (old) old.remove();\n var t = h(\"div\", \"toast\");\n t.setAttribute(\"role\", \"alert\");\n t.appendChild(h(\"span\", null, msg));\n var x = h(\"button\", null, \"\\u2715\");\n x.setAttribute(\"aria-label\", \"Dismiss\");\n x.onclick = function () { t.remove(); };\n t.appendChild(x);\n document.body.appendChild(t);\n }\n\n /* POST an op; the file write wakes the SSE watcher, which re-renders\n every viewer \u2014 success needs no local handling at all */\n function postEdit(payload) {\n fetch(\"/edit\", {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(payload)\n }).then(function (r) {\n return r.json().catch(function () { return {}; }).then(function (j) {\n if (!r.ok) throw new Error(j.error || (\"HTTP \" + r.status));\n });\n }).catch(function (e) { toast(e.message); });\n }\n\n api.toggleButton = function () {\n var b = h(\"button\", \"cbtn\" + (api.on ? \" on\" : \"\"), \"\\u270e edit\");\n b.setAttribute(\"aria-pressed\", api.on ? \"true\" : \"false\");\n b.title = \"Edit mode: tokens, copy, and blocks \\u2014 writes the project file\";\n b.onclick = function () {\n api.on = !api.on;\n if (api.on) commentMode = false; /* modes are exclusive */\n render();\n };\n return b;\n };\n\n /* 3-digit hex \u2192 6-digit (color inputs only accept #rrggbb) */\n function hex6(v, fallback) {\n if (typeof v !== \"string\") return fallback;\n var m = v.match(/^#([0-9a-fA-F]{3})$/);\n if (m) return \"#\" + m[1].charAt(0) + m[1].charAt(0) + m[1].charAt(1) + m[1].charAt(1) +\n m[1].charAt(2) + m[1].charAt(2);\n return /^#[0-9a-fA-F]{6}$/.test(v) ? v : fallback;\n }\n\n api.tokenPanel = function () {\n var p = h(\"div\", \"tokpanel\");\n p.appendChild(h(\"h3\", null, \"Design tokens\"));\n var t = (data && data.tokens) || {}, c = t.colors || {};\n [[\"accent\", \"#2f6b52\"], [\"bg\", \"#ffffff\"], [\"surface\", \"#ffffff\"],\n [\"text\", \"#2a2e2a\"], [\"muted\", \"#6b706b\"]].forEach(function (def) {\n var name = def[0];\n var lab = h(\"label\");\n lab.appendChild(h(\"span\", null, name));\n var inp = h(\"input\");\n inp.setAttribute(\"type\", \"color\");\n inp.value = hex6(c[name], def[1]);\n inp.setAttribute(\"aria-label\", \"Token color \" + name);\n inp.onchange = function () {\n postEdit({ op: \"set-token\", path: \"colors.\" + name, value: inp.value });\n };\n lab.appendChild(inp);\n p.appendChild(lab);\n });\n var rl = h(\"label\");\n rl.appendChild(h(\"span\", null, \"radius\"));\n var ri = h(\"input\");\n ri.setAttribute(\"type\", \"number\");\n ri.setAttribute(\"min\", \"0\"); ri.setAttribute(\"max\", \"32\"); ri.setAttribute(\"step\", \"1\");\n ri.value = typeof t.radius === \"number\" ? String(t.radius) : \"8\";\n ri.setAttribute(\"aria-label\", \"Token radius in px\");\n ri.onchange = function () {\n var n = Number(ri.value);\n if (isFinite(n)) postEdit({ op: \"set-token\", path: \"radius\", value: n });\n };\n rl.appendChild(ri); p.appendChild(rl);\n var fl = h(\"label\");\n fl.appendChild(h(\"span\", null, \"font\"));\n var fi = h(\"input\");\n fi.setAttribute(\"type\", \"text\");\n fi.value = t.font || \"\";\n fi.setAttribute(\"placeholder\", \"Inter, sans-serif\");\n fi.setAttribute(\"aria-label\", \"Token font stack\");\n fi.onchange = function () {\n if (fi.value.trim()) postEdit({ op: \"set-token\", path: \"font\", value: fi.value.trim() });\n };\n fl.appendChild(fi); p.appendChild(fl);\n return p;\n };\n\n function toolButton(txt, label, fn) {\n var x = h(\"button\", null, txt);\n x.setAttribute(\"aria-label\", label);\n x.title = label;\n x.onclick = function (e) { e.stopPropagation(); fn(); };\n return x;\n }\n\n function inlineEdit(w, b, i, screen) {\n var old = w.querySelector(\".eedit\");\n if (old) { old.remove(); return; }\n var isCopy = b.type === \"text\" || b.type === \"hero\";\n var box = h(\"div\", \"eedit\");\n var inp = h(\"input\");\n inp.setAttribute(\"type\", \"text\");\n inp.value = isCopy ? (b.copy || \"\") : (b.label || \"\");\n inp.setAttribute(\"aria-label\", (isCopy ? \"Copy\" : \"Label\") + \" for \" + b.type + \" block \" + (i + 1) +\n \" \\u2014 Enter saves, Escape cancels\");\n inp.onkeydown = function (ev) {\n if (ev.key === \"Enter\") {\n if (isCopy) postEdit({ op: \"set-copy\", screen: screen.id, blockIndex: i, copy: inp.value });\n else postEdit({ op: \"set-label\", screen: screen.id, blockIndex: i, label: inp.value });\n box.remove();\n }\n if (ev.key === \"Escape\") box.remove();\n };\n box.appendChild(inp);\n w.appendChild(box);\n inp.focus();\n inp.select();\n }\n\n /* hover/focus toolbar on each top-level block of an explicit layout */\n api.wrap = function (el, b, i, screen) {\n var total = screen.layout.blocks.length;\n var w = h(\"div\", \"ewrap\");\n w.appendChild(el);\n var tools = h(\"div\", \"etools\");\n if (i > 0) tools.appendChild(toolButton(\"\\u2191\", \"Move \" + b.type + \" block up\", function () {\n postEdit({ op: \"move-block\", screen: screen.id, from: i, to: i - 1 });\n }));\n if (i < total - 1) tools.appendChild(toolButton(\"\\u2193\", \"Move \" + b.type + \" block down\", function () {\n postEdit({ op: \"move-block\", screen: screen.id, from: i, to: i + 1 });\n }));\n if ([\"text\", \"hero\", \"button\", \"field\"].indexOf(b.type) >= 0) {\n var what = b.type === \"text\" || b.type === \"hero\" ? \"copy\" : \"label\";\n tools.appendChild(toolButton(\"\\u270e\", \"Edit \" + b.type + \" \" + what, function () {\n inlineEdit(w, b, i, screen);\n }));\n }\n tools.appendChild(toolButton(\"\\u2715\", \"Remove \" + b.type + \" block\", function () {\n if (window.confirm(\"Remove this \" + b.type + \" block?\"))\n postEdit({ op: \"remove-block\", screen: screen.id, blockIndex: i });\n }));\n w.appendChild(tools);\n return w;\n };\n\n api.addButton = function (screen) {\n var wrap = h(\"div\");\n var pal = null;\n var btn = h(\"button\", \"addblock\", \"+ add block\");\n btn.setAttribute(\"aria-expanded\", \"false\");\n btn.onclick = function () {\n if (pal) { pal.remove(); pal = null; btn.setAttribute(\"aria-expanded\", \"false\"); return; }\n pal = h(\"div\", \"palette\");\n pal.setAttribute(\"role\", \"menu\");\n BLOCK_TYPES.forEach(function (t) {\n var tb = h(\"button\", null, t);\n tb.setAttribute(\"role\", \"menuitem\");\n tb.onclick = function () {\n postEdit({ op: \"add-block\", screen: screen.id, index: screen.layout.blocks.length, block: { type: t } });\n pal.remove(); pal = null;\n btn.setAttribute(\"aria-expanded\", \"false\");\n };\n pal.appendChild(tb);\n });\n wrap.appendChild(pal);\n btn.setAttribute(\"aria-expanded\", \"true\");\n pal.firstChild.focus();\n };\n wrap.appendChild(btn);\n return wrap;\n };\n\n return api;\n})();\n/*__UXLOOM_EDIT_END__*/\n/*__UXLOOM_LIVE__*/\nnew EventSource(\"/events\").onmessage = function () { load(); };\n/*__UXLOOM_LIVE_END__*/\nload();\n</script>\n</body>\n</html>";
/**

@@ -18,0 +23,0 @@ * Turn the live template into one self-contained HTML file: project data

@@ -12,4 +12,9 @@ /**

*
* When the project, a journey, or a screen carries design rationale
* (R20), an "ⓘ why" toggle reveals the evidence panel: decision,
* reasoning, alternatives argued, sources, and a confidence chip.
*
* renderStandalone() turns this same template into a static, shareable
* HTML file: project data embedded, SSE and comment mode removed.
* HTML file: project data embedded, SSE and comment mode removed. The
* rationale panel is kept — it is the stakeholder-confidence deliverable.
*/

@@ -153,2 +158,36 @@ export const PREVIEW_TEMPLATE = `<!DOCTYPE html>

/* rationale (the "why" evidence panel, R20) */
.whypanel { width: 340px; flex-shrink: 0; background: var(--panel); border-left: 1px solid var(--line);
overflow-y: auto; padding: 14px 16px; outline: none; }
.whytop { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.whytitle { font-size: 11px; text-transform: uppercase; letter-spacing: .06em; color: var(--dim); }
.whyclose { color: var(--dim); font-size: 14px; padding: 2px 6px; border-radius: 6px; }
.whyclose:hover, .whyclose:focus-visible { background: #e6e9e6; color: var(--ink); }
.rat-scope { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px;
color: var(--dim); margin-bottom: 2px; }
.rat-head { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }
.rat-decision { font-size: 14px; font-weight: 600; }
.rat-conf { display: inline-block; font-size: 10.5px; border-radius: 999px; padding: 1px 9px;
border: 1px solid var(--line); color: var(--dim); white-space: nowrap; }
.rat-conf.medium { border-color: var(--accent); color: var(--accent); }
.rat-conf.high { background: var(--accent); border-color: var(--accent); color: #fff; }
.rat-reason { font-size: 12.5px; margin: 6px 0 10px; }
.rat-alts { width: 100%; border-collapse: collapse; font-size: 11.5px; margin: 8px 0 10px; }
.rat-alts caption { text-align: left; font-size: 10.5px; text-transform: uppercase;
letter-spacing: .05em; color: var(--dim); margin-bottom: 4px; }
.rat-alts th, .rat-alts td { border: 1px solid var(--line); padding: 4px 6px; text-align: left;
vertical-align: top; }
.rat-alts th { background: #f1f3f1; font-size: 10px; text-transform: uppercase;
letter-spacing: .05em; color: var(--dim); }
.rat-opt { font-weight: 600; }
.rat-pros, .rat-cons { list-style: disc; padding-left: 14px; margin: 0; }
.rat-pros li { color: #2e7d4f; }
.rat-cons li { color: var(--err); }
.rat-src { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; margin-top: 4px; }
.rat-lbl { font-size: 10.5px; text-transform: uppercase; letter-spacing: .05em; color: var(--dim); }
.rat-src a { color: var(--accent); font-size: 12px; }
.whydetails { border-top: 1px solid var(--line); margin-top: 12px; padding-top: 10px; }
.whydetails summary { cursor: pointer; font-size: 12px; font-weight: 600; margin-bottom: 6px; }
.whymark { margin-left: 5px; font-size: 11px; opacity: .55; }
/* comment mode */

@@ -226,2 +265,3 @@ .cbtn { border: 1px solid var(--line); border-radius: 999px; padding: 3px 12px; font-size: 12.5px; }

</main>
<div id="why" class="whypanel" role="complementary" aria-label="Design rationale" tabindex="-1" hidden></div>
<script>

@@ -233,2 +273,3 @@ "use strict";

var commentMode = false;
var whyOpen = false;
/* assigned by the edit-mode section below; stripped from static exports */

@@ -517,2 +558,99 @@ var EDIT = null;

/* ----------- R20: design rationale — the "why" evidence panel ----------- */
/* the toggle is visible only when the current screen OR the project carries
rationale; journeys surface theirs via the panel and a muted meta chip */
function hasWhyContent(screen) {
return !!((screen && screen.rationale) || (data && data.rationale));
}
function journeysForScreen(screenId) {
return (data.journeys || []).filter(function (j) {
return Object.keys(j.states).some(function (sn) { return j.states[sn].screen === screenId; });
});
}
function hostOf(u) {
try { return new URL(u).hostname || u; } catch (e) { return u; }
}
/* one rationale rendered as evidence: decision, confidence, reasoning,
the alternatives that were argued over, and the sources it leans on.
Every field is optional — show what exists, never invent the rest. */
function ratBody(r) {
var box = h("div", "rat");
var head = h("div", "rat-head");
head.appendChild(h("h3", "rat-decision", r.decision || "(no decision recorded)"));
if (r.confidence === "low" || r.confidence === "medium" || r.confidence === "high")
head.appendChild(h("span", "rat-conf " + r.confidence, r.confidence + " confidence"));
box.appendChild(head);
if (r.reasoning) box.appendChild(h("p", "rat-reason", r.reasoning));
if (r.alternatives && r.alternatives.length) {
var tbl = h("table", "rat-alts");
tbl.appendChild(h("caption", null, "Alternatives considered"));
var thr = h("tr");
["Option", "Pros", "Cons"].forEach(function (t) { thr.appendChild(h("th", null, t)); });
tbl.appendChild(thr);
r.alternatives.forEach(function (a) {
var tr = h("tr");
tr.appendChild(h("td", "rat-opt", a.option || ""));
var ptd = h("td"), pul = h("ul", "rat-pros");
(a.pros || []).forEach(function (p) { pul.appendChild(h("li", null, p)); });
ptd.appendChild(pul); tr.appendChild(ptd);
var ctd = h("td"), cul = h("ul", "rat-cons");
(a.cons || []).forEach(function (c) { cul.appendChild(h("li", null, c)); });
ctd.appendChild(cul); tr.appendChild(ctd);
tbl.appendChild(tr);
});
box.appendChild(tbl);
}
if (r.sources && r.sources.length) {
var src = h("div", "rat-src");
src.appendChild(h("span", "rat-lbl", "Sources"));
r.sources.forEach(function (u) {
var a = h("a", null, hostOf(u));
a.setAttribute("href", u);
a.setAttribute("target", "_blank");
a.setAttribute("rel", "noopener");
a.title = u;
src.appendChild(a);
});
box.appendChild(src);
}
return box;
}
function closeWhy() {
whyOpen = false;
render();
var t = document.getElementById("whytoggle");
if (t) t.focus();
}
function renderWhy(screen) {
var panel = document.getElementById("why");
panel.innerHTML = "";
if (!hasWhyContent(screen)) whyOpen = false;
panel.hidden = !whyOpen;
if (!whyOpen) return;
var top = h("div", "whytop");
top.appendChild(h("h2", "whytitle", "Design rationale"));
var x = h("button", "whyclose", "\\u2715");
x.setAttribute("aria-label", "Close rationale panel");
x.onclick = function () { closeWhy(); };
top.appendChild(x);
panel.appendChild(top);
if (screen && screen.rationale) {
panel.appendChild(h("div", "rat-scope", screen.id));
panel.appendChild(ratBody(screen.rationale));
}
if (data && data.rationale) {
var pd = h("details", "whydetails");
pd.appendChild(h("summary", null, "Product direction"));
pd.appendChild(ratBody(data.rationale));
panel.appendChild(pd);
}
if (screen) journeysForScreen(screen.id).forEach(function (j) {
if (!j.rationale) return;
var jd = h("details", "whydetails");
jd.appendChild(h("summary", null, "Flow rationale \\u00b7 " + j.id));
jd.appendChild(ratBody(j.rationale));
panel.appendChild(jd);
});
}
/* ------------------------------ render ------------------------------ */

@@ -546,3 +684,9 @@ function render() {

(data.screens || []).forEach(function (s) {
var b = h("button", "snav" + (screen && s.id === screen.id ? " on" : ""), s.id);
var b = h("button", "snav" + (screen && s.id === screen.id ? " on" : ""));
b.appendChild(document.createTextNode(s.id));
if (s.rationale) {
var wm = h("span", "whymark", "\\u24d8");
wm.title = "has design rationale";
b.appendChild(wm);
}
b.onclick = function () { pick(s.id, "default"); };

@@ -580,2 +724,15 @@ side.appendChild(b);

}
if (hasWhyContent(screen)) {
var wb = h("button", "cbtn" + (whyOpen ? " on" : ""), "\\u24d8 why");
wb.id = "whytoggle";
wb.setAttribute("aria-pressed", whyOpen ? "true" : "false");
wb.setAttribute("aria-controls", "why");
wb.title = "Why: the design rationale behind this screen";
wb.onclick = function () {
whyOpen = !whyOpen;
render();
if (whyOpen) document.getElementById("why").focus();
};
bar.appendChild(wb);
}
if (STATIC_INFO) bar.appendChild(h("span", "chip", "static export \\u00b7 " + STATIC_INFO.generated));

@@ -646,3 +803,9 @@ else bar.appendChild(h("span", "chip live", "\\u25cf live"));

});
journeysForScreen(screen.id).forEach(function (j) {
if (j.rationale && j.rationale.decision)
meta.appendChild(h("span", "chip flowchip", "flow: " + j.rationale.decision));
});
}
renderWhy(screen);
}

@@ -674,2 +837,5 @@

}
document.addEventListener("keydown", function (e) {
if (e.key === "Escape" && whyOpen) closeWhy();
});
/*__UXLOOM_EDIT__*/

@@ -676,0 +842,0 @@ /* ------------- structured edit mode (live preview only) ------------- */

@@ -1,1 +0,1 @@

{"version":3,"file":"preview-template.js","sourceRoot":"","sources":["../src/preview-template.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"preview-template.js","sourceRoot":"","sources":["../src/preview-template.ts"],"names":[],"mappings":"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"}

@@ -10,3 +10,4 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";

import { loadMap, runAudit } from "./audit.js";
import { commentFindings, loadWorkspace } from "./workspace.js";
import { commentFindings, criticOptionsFor, loadReviews, loadWorkspace, reviewsPathFor, saveReviews, } from "./workspace.js";
import { rationaleCoverage } from "@uxloom/critics";
// Version is DERIVED from package.json — never hardcode it here again.

@@ -116,2 +117,56 @@ // (Hand-bumped strings drifted three releases in a row before this.)

});
server.tool("design_review", "One iterative design-review round (max 3 per project — enforced). Computes validation + rationale coverage, persists the round with deltas to <project>.reviews.json, and returns a structured rubric to critique the design against: completeness, evidence, consistency, market fit, accessibility, honesty. Address the rubric, improve the design, then call again. Round 4 is refused: present results to the user instead.", {
notes: z.string().optional().describe("What this round focused on / what changed since the last round"),
}, async ({ notes }) => {
const ws = loadWorkspace(store.path);
const reviewsPath = reviewsPathFor(ws.projectPath);
const rounds = loadReviews(reviewsPath);
if (rounds.length >= 3) {
return json({
allowed: false,
roundsUsed: rounds.length,
maxRounds: 3,
history: rounds,
message: "All 3 review rounds are used. Present the design and its evidence to the user — further iteration needs their direction, not more self-review.",
});
}
const report = critique(ws.project, criticOptionsFor(ws.config));
const coverage = rationaleCoverage(ws.project);
const previous = rounds.at(-1);
const entry = {
round: rounds.length + 1,
at: new Date().toISOString(),
errors: report.summary.errors,
warnings: report.summary.warnings,
rationale: { documented: coverage.documented, total: coverage.total },
notes,
};
saveReviews(reviewsPath, [...rounds, entry]);
return json({
allowed: true,
round: entry.round,
maxRounds: 3,
delta: previous
? {
errors: entry.errors - previous.errors,
warnings: entry.warnings - previous.warnings,
rationaleDocumented: entry.rationale.documented - previous.rationale.documented,
}
: null,
validation: report.summary,
openFindings: report.findings.slice(0, 30),
rationaleCoverage: coverage,
rubric: [
"COMPLETENESS — every journey reaches a final state; every screen contract covers empty/loading/error or carries a written exemption; no validation errors remain.",
"EVIDENCE — every screen, journey, and the project carry rationale with ≥1 rejected alternative (real pros AND cons) and sources for factual claims; nothing thin.",
"CONSISTENCY — tokens used coherently; component semantics, state naming, and copy tone uniform across screens; labels carry i18n keys and budgets.",
"MARKET FIT — compare against references/patterns.md conventions for this product category (and live research when available): does each major pattern choice match or deliberately diverge, and is divergence argued in the rationale?",
"ACCESSIBILITY — interactive elements labeled, contrast declared and passing, targets sized, decorative motion has fallbacks.",
"HONESTY — exemptions have real reasons; no contract weakened to silence a finding; reviewer comments addressed, not dismissed.",
],
instruction: entry.round < 3
? `Round ${entry.round} of 3. Critique the design against every rubric line, fix what you find, then call design_review again.`
: "Final round (3 of 3). Fix what you find, then present the design with its full evidence — decisions, alternatives, sources, confidence — to the user.",
});
});
server.tool("project_audit", "Audit the implementation against the design contract (drift detection). Static tiers: the uxloom.map.json screen registry and data-ux-screen/data-ux-state markers in source. Returns per-state verdicts (implemented with file:line evidence / unimplemented / unproven) and findings with fixes. When implementing screens from the contract, emit data-ux-state markers so the code stays self-auditing.", {

@@ -130,3 +185,3 @@ root: z

const ws = loadWorkspace(store.path);
const report = critique(ws.project, ws.config.thresholds);
const report = critique(ws.project, criticOptionsFor(ws.config));
const extra = [...ws.loadFindings, ...commentFindings(ws.comments)];

@@ -133,0 +188,0 @@ return json({

@@ -1,1 +0,1 @@

{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}

@@ -15,3 +15,21 @@ import { type Finding, type Project } from "@uxloom/journeygraph";

thresholds?: CriticOptions;
/** "required" forces rationale enforcement before any rationale exists. */
rationale?: "required";
}
/** Critic options derived from config (rationale mode folded in). */
export declare function criticOptionsFor(config: UxloomConfig): CriticOptions;
export interface ReviewRound {
round: number;
at: string;
errors: number;
warnings: number;
rationale: {
documented: number;
total: number;
};
notes?: string;
}
export declare function reviewsPathFor(projectPath: string): string;
export declare function loadReviews(path: string): ReviewRound[];
export declare function saveReviews(path: string, rounds: ReviewRound[]): void;
export interface Baseline {

@@ -18,0 +36,0 @@ check: string[];

@@ -15,2 +15,23 @@ /**

import { globToRegExp } from "./audit.js";
/** Critic options derived from config (rationale mode folded in). */
export function criticOptionsFor(config) {
return { ...config.thresholds, requireRationale: config.rationale === "required" };
}
export function reviewsPathFor(projectPath) {
return projectPath.replace(/\.json$/, "") + ".reviews.json";
}
export function loadReviews(path) {
if (!existsSync(path))
return [];
try {
const parsed = JSON.parse(readFileSync(path, "utf8"));
return Array.isArray(parsed.rounds) ? parsed.rounds : [];
}
catch {
return [];
}
}
export function saveReviews(path, rounds) {
writeFileSync(path, JSON.stringify({ rounds }, null, 2) + "\n");
}
export function commentsPathFor(projectPath) {

@@ -17,0 +38,0 @@ return projectPath.replace(/\.json$/, "") + ".comments.json";

@@ -1,1 +0,1 @@

{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
{"version":3,"file":"workspace.js","sourceRoot":"","sources":["../src/workspace.ts"],"names":[],"mappings":"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"}
{
"name": "uxloom",
"version": "0.7.0",
"version": "0.8.0",
"description": "UXLoom — agent-native UI/UX design validation via MCP. Your generator gave you 6 screens; UXLoom proves you're missing 9 states.",

@@ -5,0 +5,0 @@ "keywords": [

@@ -61,2 +61,3 @@ # uxloom

| `project_audit` | Implementation drift: per-state verdicts (implemented / unimplemented / unproven) with file:line evidence |
| `design_review` | Iterative evidence-based review — max 3 rounds (enforced, persisted), six-part rubric incl. market fit; pairs with per-decision `rationale` in the format |
| `project_validate` | Run every critic; iterate until zero errors |

@@ -63,0 +64,0 @@ | `screen_critique` | Findings scoped to one screen |

@@ -146,2 +146,22 @@ # JourneyGraph format reference (v0.1)

**Design rationale (evidence-based design, v0.8)** — project, journeys,
and screens carry the evidence behind decisions:
```json
"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](design-intelligence.md); iterate with
uxloom:design_review (max 3 rounds, enforced).
**Config and baseline** — `uxloom.config.json` overrides thresholds

@@ -148,0 +168,0 @@ (`{ "thresholds": { "contrastRatio": 7, "expansionFactor": 1.5,

@@ -28,2 +28,13 @@ ---

preview. Never treat designer feedback as optional.
6. **Decisions carry evidence.** For any substantial design (not quick
sketches), practice evidence-based design: research the product
category first, compare real alternatives for every major decision,
and record `rationale` (decision, reasoning, rejected alternatives
with pros/cons, sources, confidence) on the project, journeys, and
screens. Then run the bounded review loop: uxloom:design_review,
max 3 rounds — critique against its rubric, improve, and finish with
a confidence report to the user. Full process:
[references/design-intelligence.md](references/design-intelligence.md) ·
category conventions: [references/patterns.md](references/patterns.md).
Never fabricate statistics — cite real research or label judgment.

@@ -30,0 +41,0 @@ ## Workflow