--- description: Generate a feature-local interactive HTML prototype from UX contracts, producing specs//prototype/index.html plus a prototype manifest and state-coverage report. No production source mutation. handoffs: - label: Generate OpenAPI Spec agent: speckit.openapi prompt: Derive OpenAPI 3.1 from the prototype states and UX contracts send: true - label: Build Technical Plan agent: speckit.plan prompt: Create a Python/Svelte implementation plan using the validated prototype as interaction reference send: true --- ## User Input ```text $ARGUMENTS ``` You **MUST** consider the user input before proceeding (if not empty). ## Applicability This command is applicable ONLY when the feature has a UI surface. For backend-only features, skip gracefully with: "No UI surface detected — prototype not applicable. Proceed to `/speckit.openapi` or `/speckit.plan`." **Decision gate**: If `FEATURE_DIR/contracts/ux/` exists (from `/speckit.ux`), generate the full prototype. If only `ux_reference.md` exists, generate a lightweight prototype from the reference. If neither exists, skip. ## Principle You are generating a **read-only, interactive HTML artifact** that validates UX contract states against actual browser behavior. The prototype is a **design verification tool**, not production code. It proves that every declared `@UX_STATE` can be reached, that `@UX_FEEDBACK` mechanisms work, and that `@UX_RECOVERY` paths are traversable — all without touching `frontend/src/`. **Design fidelity is mandatory, not optional**: the prototype MUST visually match the application's real design system. It is built by **copying the exact utility classes and design tokens from the production Svelte components**, not by inventing a parallel "prototype style". A prototype that looks different from the app fails its purpose — reviewers cannot judge states they will never see in production. If you find yourself writing a custom hex color, custom radius, or custom shadow that is not in `frontend/tailwind.config.js`, you are doing it wrong. ## Outline ### Phase 0: Pre-Flight 1. **Setup**: Run `.specify/scripts/bash/check-prerequisites.sh --json --paths-only` from repo root. Parse `FEATURE_DIR`. 2. **Verify applicability**: Check for `FEATURE_DIR/contracts/ux/` or `FEATURE_DIR/ux_reference.md`. If neither exists and no UI surface is indicated, report skip and exit. 3. **Load context**: - `FEATURE_DIR/spec.md` — user stories and acceptance criteria - `FEATURE_DIR/ux_reference.md` — interaction reference - `FEATURE_DIR/contracts/ux/screen-models.md` — model inventory (if exists) - `FEATURE_DIR/contracts/ux/api-ux.md` — API shapes for realistic mock data (if exists) - `FEATURE_DIR/contracts/ux/-ux.md` — per-screen UX contracts (if exists) - `.opencode/skills/semantics-svelte/SKILL.md` — §VI canonical FSM template, §VII design tokens - `frontend/tailwind.config.js` — **design token SSOT**: semantic color palette (primary/secondary/destructive/success/warning/info/ghost/surface/border/text), typography, spacing, radius - `frontend/src/app.css` — global styles and motion preferences - `frontend/src/lib/ui/` — existing design-system atom inventory (Button, Card, Input, Select, Badge, PageHeader, Skeleton, EmptyState, Pagination, etc.) - `frontend/src/lib/components/` — existing composite widget inventory - `frontend/src/lib/ui/index.ts` — component export index - **Every `.svelte` component the prototype will use** — read the full source to copy its exact class strings ### Phase 0.5: Design System Alignment (MANDATORY — before any HTML) Extract the **design system truth** from production sources. This phase produces a working set of tokens and class recipes that the prototype MUST use verbatim. **Step 1 — Extract design tokens** from `frontend/tailwind.config.js`: - Semantic palette: `primary.*`, `secondary.*`, `destructive.*`, `success.*`, `warning.*`, `info.*`, `ghost.*`, `surface.*`, `border.*`, `text.*`, `brand.*`, `terminal.*` (if applicable) - Record hex values exactly: e.g. `primary.DEFAULT = #2563eb`, `primary.hover = #1d4ed8`, `surface.page = #f8fafc`, `text.muted = #64748b` - Record widths (sidebar 240px), font families (JetBrains Mono for terminal) **Step 2 — Extract component class recipes** from `frontend/src/lib/ui/*.svelte`: - Read the full source of each component the prototype uses (Button, Card, Badge, PageHeader, Input, Select, Skeleton, EmptyState, Pagination, ConfirmDialog, Toast if used) - Copy the exact `class` strings from the Svelte template, e.g.: - `Button` base: `inline-flex items-center justify-center font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 rounded-md` - `Button` primary: `bg-primary text-white hover:bg-primary-hover focus-visible:ring-primary-ring` - `Button` sizes: `sm: h-8 px-3 text-xs`, `md: h-10 px-4 py-2 text-sm`, `lg: h-12 px-6 text-base` - `Card`: `rounded-lg border border-border bg-surface-card text-text shadow-sm`, padding `p-6` (md) - `Badge` variants: `bg-success-light text-success`, `bg-warning-light text-warning`, `bg-destructive-light text-destructive`, `bg-info-light text-info`, `bg-primary-light text-primary`, `bg-surface-muted text-text-muted`; shape `rounded-full text-xs font-medium` - `PageHeader`: `flex items-center justify-between mb-8`, title `text-3xl font-bold tracking-tight text-text` - `EmptyState`: read source, copy its structure and classes - `Skeleton`: `animate-pulse` + muted surface classes - **If the app uses dark mode / terminal palette** (log viewer, task drawer): replicate `terminal.bg`/`terminal.surface`/`terminal.border` where the feature touches those surfaces **Step 3 — Build the prototype stylesheet as a Tailwind-utility shim**: - The prototype is a single self-contained HTML file (no build step). Inline the **Tailwind utility classes the app actually uses** as a minimal CSS shim: for every class string copied in Step 2, write the CSS rule that implements it (e.g. `.bg-primary { background-color: #2563eb; }`, `.hover\:bg-primary-hover:hover { background-color: #1d4ed8; }`). - **Color values MUST come only from `tailwind.config.js`.** No invented hex codes. If a color is needed that is not a token, use the nearest semantic token. - Keep the shim scoped and complete: every class used in the HTML body MUST have a definition in the `
...
``` **Rules**: - **Single file**: `index.html` is self-contained. All CSS and JS are inline. No external dependencies by default. - **USE THE REAL CLASS RECIPES — verbatim**: Every interactive element, container, and label in the prototype MUST carry the **exact same Tailwind class strings** as the production component it represents (from Phase 0.5 Step 2). Do NOT simplify, rename, or "clean up" production classes. Examples: - Buttons: `class="inline-flex items-center justify-center font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 rounded-md bg-primary text-white hover:bg-primary-hover focus-visible:ring-primary-ring h-10 px-4 py-2 text-sm"` - Cards: `class="rounded-lg border border-border bg-surface-card text-text shadow-sm p-6"` - Badges: `class="inline-flex items-center gap-1.5"` wrapper + `class="rounded-full px-2.5 py-1 text-xs font-medium bg-success-light text-success"` - PageHeader: `class="flex items-center justify-between mb-8"` + `class="text-3xl font-bold tracking-tight text-text"` - **Tokens from `tailwind.config.js` only**: The CSS shim's color/radius/shadow/spacing values MUST be the exact hex/px from `frontend/tailwind.config.js`. Zero invented values. If you cannot find a token for a needed style, use the nearest semantic token or note it in the manifest as a design gap. - **Match component behavior**: Disabled buttons get `disabled:opacity-50` + `disabled:pointer-events-none`; loading buttons show the spinner SVG with `animate-spin`; skeletons use `animate-pulse`; badges use the semantic variant pair (`bg-*-light text-*`). - **No production source mutation**: The prototype lives in `specs//prototype/`. It NEVER writes to `frontend/src/`. - **Accessibility**: All interactive elements MUST have: appropriate ARIA roles, `aria-live` regions for dynamic content, keyboard navigation (Tab/Enter/Space), focus management (match `focus-visible:ring-2` classes), minimum 44×44px touch targets on mobile, and `alt` text for images/icons. - **Responsive**: Match the app's actual breakpoints (mobile-first; Tailwind sm 640px / md 768px / lg 1024px). Test on both viewports via the state switcher's viewport toggle. - **State switcher**: A fixed toolbar at the top of the prototype that allows: - Switching between screens (if multiple) - Toggling between states for each screen - Toggling viewport size (desktop 1280px / mobile 375px) - Shows CURRENT state name, can trigger transitions (loading → loaded, loaded → error, etc.) - **The switcher itself is a prototype chrome, not app UI** — it may use plain styling, but every element INSIDE the screen sections must use production classes - **Realistic mock data**: Use data shapes from `api-ux.md` to populate loaded states with plausible content. Empty states show realistic empty-state components. Error states show realistic error messages. ### Phase 3: Generate Prototype Manifest Create `specs//prototype/manifest.md`: ```markdown #region Std.Opencode.PrototypeManifest [C:3] [TYPE ADR] [SEMANTICS prototype,manifest,[DOMAIN]] @defgroup Prototype Interactive HTML prototype manifest for [FEATURE]. ## Prototype Metadata - **Feature**: [feature name] - **Source contracts**: contracts/ux/ - **Screens represented**: N - **Total states**: N - **Accessibility validations**: keyboard nav, ARIA roles, touch targets, focus management - **Responsive breakpoints**: 375px (mobile), 1280px (desktop) ## State Coverage | Screen | @UX_STATE Contract | Prototype State | Reachable? | Recovery Path | |--------|-------------------|-----------------|------------|---------------| | Dashboard | idle | idle (default) | ✅ | — | | Dashboard | loading | loading (3s auto) | ✅ | — | | Dashboard | loaded | loaded (with mock data) | ✅ | — | | Dashboard | empty | empty (no data mock) | ✅ | — | | Dashboard | error | error (network fail) | ✅ | retry button → loading | | Dashboard | stale | stale (cached + indicator) | ✅ | refresh button | ## Screen ↔ Story Traceability | Prototype Screen | User Story | UX Contract | Acceptance Criteria Verified | |-----------------|------------|-------------|------------------------------| | /dashboard | US1: View Dashboards | DashboardUx | AC1: list loads, AC2: empty state | | /migration | US2: Migrate Items | MigrationUx | AC1: step wizard, AC2: error recovery | ## Validation Results - [ ] All @UX_STATE contracts reachable via state switcher - [ ] All @UX_RECOVERY paths traversable - [ ] Keyboard navigation: Tab order verified - [ ] Touch targets: ≥44×44px on mobile viewport - [ ] ARIA: live regions for loading/error states - [ ] No broken links or dead-end states - [ ] Responsive layout: mobile viewport does not overflow ## Design System Reuse | Element | Source | Prototype Mapping | |---------|--------|-------------------| | Button | $lib/ui/Button.svelte | Same class string: `bg-primary text-white hover:bg-primary-hover ... h-10 px-4 py-2 text-sm` | | Card | $lib/ui/Card.svelte | Same class string: `rounded-lg border border-border bg-surface-card text-text shadow-sm p-6` | | Badge | $lib/ui/Badge.svelte | Same class string: `rounded-full px-2.5 py-1 text-xs font-medium bg-{variant}-light text-{variant}` | | Skeleton | $lib/ui/Skeleton.svelte | `animate-pulse` + muted surface | | EmptyState | $lib/ui/EmptyState.svelte | Copy structure + classes from source | | PageHeader | $lib/ui/PageHeader.svelte | Same class string: `flex items-center justify-between mb-8` + `text-3xl font-bold tracking-tight text-text` | | Input | $lib/ui/Input.svelte | Copy classes from source | | Select | $lib/ui/Select.svelte | Copy classes from source | ## Design Token Audit (MANDATORY) Every color/radius/shadow/spacing value used in the prototype MUST trace to `frontend/tailwind.config.js`. Complete this table during build: | Token (tailwind.config.js) | Hex / Value | Used in prototype (elements) | |----------------------------|-------------|------------------------------| | `primary.DEFAULT` | `#2563eb` | primary buttons, active states | | `primary.hover` | `#1d4ed8` | primary button hover | | `primary.light` | `#eff6ff` | `bg-primary-light` badge variant | | `destructive.DEFAULT` | `#dc2626` | destructive buttons, error accents | | `destructive.light` | `#fef2f2` | `bg-destructive-light` badge variant | | `success.DEFAULT` / `success.light` | `#22c55e` / `#f0fdf4` | success badges | | `warning.DEFAULT` / `warning.light` | `#f59e0b` / `#fffbeb` | warning badges | | `info.DEFAULT` / `info.light` | `#0ea5e9` / `#f0f9ff` | info badges | | `surface.page` | `#f8fafc` | page background | | `surface.card` | `#ffffff` | card background | | `border.DEFAULT` | `#e2e8f0` | borders | | `text.DEFAULT` / `text.muted` | `#0f172a` / `#64748b` | body / secondary text | | `brand.gradient-*` | `#0ea5e9 → #06b6d4 → #4f46e5` | brand elements (if applicable) | | `terminal.*` | dark palette | only if feature touches log/task surfaces | **Audit gate**: scan the final `index.html` for any hex color (`#[0-9a-fA-F]{3,6}`) or hardcoded px radius that does NOT appear in the token table above. Every such value is a FAIL — replace with the nearest semantic token or document in the manifest as an intentional design gap with the production source that defines it. #endregion Std.Opencode.PrototypeManifest ``` ### Phase 4: Browser Validation Open `specs//prototype/index.html` in the browser and validate: 1. **State coverage**: Cycle through every state via the state switcher. Confirm each declared `@UX_STATE` is visually represented. 2. **Recovery paths**: From each error state, verify the recovery action leads to the correct next state (retry → loading, dismiss → idle, etc.). 3. **Keyboard navigation**: Tab through all interactive elements. Confirm focus rings are visible (match `focus-visible:ring-2` classes). Confirm Enter/Space activate buttons and links. 4. **Responsive**: Toggle viewport size. Confirm layout adapts without overflow or broken alignment. 5. **Accessibility snapshot**: Use browser DevTools accessibility tree to confirm ARIA roles and labels are correct. 6. **Design fidelity (MANDATORY)**: Visually compare the prototype against the real app's equivalent components (open `frontend/` dev server or reference screenshots). Confirm: - Colors match the semantic palette (buttons, badges, alerts use identical hues) - Typography scale matches (PageHeader `text-3xl font-bold`, buttons `text-sm`, badges `text-xs`) - Spacing/padding matches (Card `p-6`, Button `px-4 py-2`, gaps `gap-1.5`/`gap-4`) - Radius matches (`rounded-md` buttons, `rounded-lg` cards, `rounded-full` badges) - Shadows match (`shadow-sm` cards) - Any mismatch is recorded in the manifest as a design gap with a fix note Record results in `manifest.md` under "Validation Results" and "Design Token Audit". ### Phase 5: Report Report: - Prototype path: `specs//prototype/index.html` - Manifest path: `specs//prototype/manifest.md` - Screens represented: N - Total states: N - State coverage: N/N contracts reachable (100% required) - Recovery paths: N/N traversable - Accessibility: keyboard nav ✅/❌, ARIA ✅/❌, touch targets ✅/❌ - **Design fidelity**: ✅ all colors/radius/shadows from `tailwind.config.js`; N production components replicated with verbatim class strings; N design gaps documented - **Token audit**: N/N hex values traced to `tailwind.config.js` (100% required) - Recommended next command: `/speckit.openapi` (if API surface) or `/speckit.plan`