| # 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 |
+168
-2
@@ -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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"} |
+57
-2
@@ -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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"} |
+18
-0
@@ -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[]; |
+21
-0
@@ -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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"} |
+1
-1
| { | ||
| "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": [ |
+1
-0
@@ -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 |
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
445805
8.84%72
2.86%4873
6.42%74
1.37%