# Mockup Gallery

Rates and tracks HTML mockups locally, then hands routed design decisions and per-route artifacts straight to your Claude Code or Codex workflow.

Status: active

## Install

```bash
claude plugin marketplace add tyroneross/RossLabs-AI-Toolkit
claude plugin install mockup-gallery@rosslabs-ai-toolkit
```

![Mockup Gallery in review — left rail tracks Selected/Active/Saved/Archive sets, main pane shows the source-preview-modal mockup, right rail captures component-level Y/N ratings ::border](/images/projects/mockup-gallery/main-review.png)

## The Problem

Mockup review is a folder of HTML files and a chat thread. Which version did the designer rate yay? Which component inside that mockup is the one to ship? Which route does it map to? Three weeks later nobody remembers and the build ships against the wrong reference.

## What I Built

`npx mockup-gallery` opens a local UI at `http://localhost:8787` that browses every HTML mockup in your project, captures ratings and notes, assigns winners to specific pages or screens, and emits two implementation-handoff artifacts when you commit to a build. Auto-detects Next.js routes, SwiftUI views, Python endpoints, and static pages.

## Architecture

Mockup Gallery is a zero-dependency Node.js server, not a framework app — `package.json` lists no `dependencies` and no `devDependencies`. There is no Express, no bundler, no database. The frontend (`gallery/gallery.html`, ~2,500 lines) is a single vanilla-JS file served in an iframe. All review state — ratings, selections, handoff specs, the project's design tokens — is plain JSON and Markdown on disk under `.mockup-gallery/`, so it's diffable and committable rather than living in a store only the tool can read.

The one piece of judgment in the system — filling in a `DESIGN.md` token or a handoff spec's Data Elements section — is deliberately not made by the gallery. `handoff.mjs` and `design-system.mjs` both state the same design principle in their source comments: "the host agent is the LLM." The gallery scaffolds structure (route detection, JSON schemas, Markdown templates with frontmatter); the already-running coding agent (Claude Code or Codex, wired in via plugin hooks) reads the source mockup's `data-component` markup and supplies the actual content.

```mermaid
flowchart LR
  A["npx mockup-gallery /<br/>plugin launch"] --> B["Node http server<br/>zero deps, stable port hash"]
  B --> C["detectProjectRoutes()<br/>Next.js / Astro / SwiftUI /<br/>Python / SPA / static / manifest"]
  B --> D["gallery.html<br/>vanilla JS, iframe preview"]
  D -->|"rate yay/nay + notes"| F[("selections.json")]
  D -->|"POST /selected"| G[("selected.json")]
  G --> H["handoff.mjs"]
  H --> I[("handoff/&lt;slug&gt;.md")]
  D -->|"POST /design-system/scaffold"| J["design-system.mjs"]
  J --> K[("DESIGN.md<br/>project root")]
  I --> L["Host coding agent<br/>Claude Code / Codex"]
  K --> L
  L -->|"fills tokens +<br/>handoff content"| M["Component build"]
  N["Plugin hooks<br/>SessionStart / UserPromptSubmit /<br/>PostToolUse"] -.surfaces pending state.-> L
```

**Models — which, where, why:**

| Stage | Model / tier | Where it runs | Why there |
|---|---|---|---|
| Gallery process itself (server, route detection, variant pairing, JSON/Markdown I/O) | None | `server/gallery-server.mjs`, `src/lib/*.mjs` | Fully deterministic — every response is filesystem reads/writes and regex/string matching. Zero LLM calls, zero AI SDK, zero API key to manage. |
| Filling `DESIGN.md` tokens and handoff-spec content (Components, Data Elements, Connectors/APIs, Open Questions) | The host coding agent's own model (whatever backs Claude Code or Codex in that session) | Outside the gallery process — triggered by the plugin's hooks and the `/mockup-gallery implement`/`handoff` commands | Explicit tradeoff, not an oversight: the gallery only needs to scaffold structure, so it never bundles a model or pays inference cost — judgment work is delegated to the coding agent that's already running the session. |

**Tools & infra — which, why:**

- **Node.js `http` (built-in)** — the whole server is the standard-library module; no framework dependency to install, patch, or audit.
- **Vanilla JS frontend** (`gallery/gallery.html`) — single file, no bundler or framework; talks to the HTTP API directly.
- **Flat JSON + Markdown files** (`.mockup-gallery/*.json`, `handoff/*.md`, root `DESIGN.md`) — no database; state is plain text so it's git-diffable and portable, trading away concurrent-writer safety (the tool assumes one local reviewer at a time).
- **Claude Code / Codex plugin surface** (`hooks/*.mjs`, `commands/`, `skills/mockup-review`) — SessionStart, UserPromptSubmit, and PostToolUse(Write\|Edit) hooks plus the `/mockup-gallery` slash command wire the gallery into the host agent's existing session instead of requiring a separate chat window.
- **Multi-framework route detector** (`detectProjectRoutes` in `gallery-server.mjs`) — filesystem/regex scan across six project shapes (below), so the same binary auto-places mockups without per-project config.

## How it works

1. `npx mockup-gallery` (or a plugin-driven launch) starts the Node `http` server, hashing the project's absolute path into a stable port in the 8787-8886 range so multiple projects' galleries can run at once without colliding.
2. `detectProjectRoutes()` walks the target repo once at startup and returns the first match, in order: Next.js App Router (`app/page.tsx`), Astro (gated on an `astro.config.*` file, then `src/pages`/`pages`), Next.js Pages Router, SwiftUI (`*View.swift`, platform-tagged ios/macos/watchos/shared), Python route decorators (`@app.route`/`@router.get`/etc.), a manual `.mockup-gallery/routes.json` manifest, Wouter/React Router SPA (`<Route path=...>` JSX), or static HTML in `public/`/`dist`/`build`/`static`.
3. The server scans the mockups directory (`mockups/`, `docs/mockups/`, `.claude/mockups/`, or `--dir`), pairs dark/light filename variants, and serves the list to `gallery.html` over `GET /mockups`; filenames fuzzy-match against the detected routes for auto-placement.
4. The reviewer opens a mockup in an iframe (`GET /mockup/:file`), rates it and its `data-component`-tagged pieces yay/nay with notes; the frontend posts to `POST /save` on every change, which writes straight to `.mockup-gallery/selections.json` (or `sessions/<slug>/selections.json` in sessions mode) — auto-saved, no explicit save step.
5. Assigning a mockup to a page (`POST /selected`) writes `selected.json` and calls `handoff.mjs`, which emits `.mockup-gallery/handoff/<route-slug>.md` — frontmatter (`route`, `source`, `status`, `filled`) plus body sections (Components, Data Elements, Connectors/APIs, Visualizations, States, Open Questions). Existing handoff files are preserved on re-emit unless `regenerateHandoffs: true` is passed.
6. `POST /design-system/scaffold` writes a starter `DESIGN.md` at the project root (Google's design-system format: YAML token frontmatter + prose sections) if none exists — 409 if one is already there without `force: true`.
7. Neither artifact's content is generated by the gallery. The plugin's `SessionStart`, `UserPromptSubmit`, and `PostToolUse(Write|Edit)` hooks (`hooks/*.mjs`) surface pending ratings, shared feedback, and unfilled handoff state to the host coding agent, which reads the mockup's `data-component` markup and fills in `DESIGN.md` tokens and the handoff sections.
8. `/mockup-gallery implement` (or `handoff`) re-reads the filled handoff spec to drive the actual component build; `implemented.json` then tracks which components have shipped, so later review sessions don't re-litigate settled UI.

## Sidebar Layout

```
SELECTED       Top — mockups assigned to pages/screens
ACTIVE         Middle — all current mockups (yay/nay just marked, not moved)
SAVED          Below active — bookmarked for later reference
ARCHIVE        Bottom — reviewed and set aside
```

Each project gets a stable port derived from its path, so multiple galleries run at once without collisions. Implementation tracking flags which components have already shipped, so review sessions do not re-debate settled UI. Auto-save to file means the rating database is a plain text artifact you can diff, commit, or hand to another reviewer.

## Wireframe-First Toggle

A sidebar toggle (default ON) drives the host coding agent to begin new review batches with a lo-fi wireframe before hi-fi HTML variants. Flip OFF when you want hi-fi directly. The preference persists per-project at `.mockup-gallery/state.json#preferences.wireframeFirst`. This catches the "the agent jumped to visuals before we agreed on structure" problem before it happens.

## Two Handoff Artifacts

When a mockup gets selected for a build, the gallery emits two artifacts the coding agent then reads:

- **Project-root `DESIGN.md`** in [Google's design-system format](https://github.com/google-labs-code/design.md) — the visual identity (colors, typography, spacing, elevation, shape) shared by mockup-gallery, ad-hoc design tools, and any AI coding agent working the repo. Scaffold via `POST /design-system/scaffold`.
- **Per-route `.mockup-gallery/handoff/<slug>.md`** — emitted on `/selected`. Carries the interaction, data, state, and connector contracts for that specific route. The agent reads it before writing the component.

## Project Detection

| Project Type | Detection | Route Source |
|---|---|---|
| Next.js App Router | `app/` dir with `page.tsx` | File-system routes |
| Astro | `astro.config.*` present, then `src/pages`/`pages` | `.astro`/`.md`/`.mdx` file routes |
| Next.js Pages | `pages/` dir | File-system routes |
| SwiftUI | `*View.swift` files | View names + platform tags (ios/macos/watchos/shared) |
| Python (Flask/FastAPI) | `@app.route`/`@router.get` decorators | Decorated endpoints |
| Manual manifest | `.mockup-gallery/routes.json` | Whatever the project author lists |
| Wouter / React Router SPA | `<Route path=...>` JSX in `App.tsx`/`router.tsx` | Parsed route props |
| Static | HTML files in `public/`, `dist/`, `build/`, `static/` | Filenames |

Detection runs once at server startup, in the order above (first match wins), so a project matching two shapes (e.g. an Astro site with a stray `pages/` dir) resolves deterministically. Filenames fuzzy-match to routes for pre-selection. Component tagging works inside each mockup for finer-grained tracking.

## Claude Code Plugin

Installed as a Claude Code plugin, the gallery adds:

- **SessionStart hook** — shows design-review status (ratings, pending items, implementation progress)
- **`/mockup-gallery`** — single entry point for launch, status, feedback, sessions, selected-build handoff, implementation guidance, and sync checks. Before coding selected UI, the agent uses `/mockup-gallery implement` or `/mockup-gallery handoff` to map changed UI elements to static/dynamic data, actions, connectors, visualizations, and unresolved questions.

## Data Storage

All data lives in `.mockup-gallery/` inside the project root: `state.json` (schema version, current session, preferences), `selections.json` (ratings + notes), `selected.json` (build assignments), `implemented.json` (tracking), `accepted-designs.json` (approved patterns), `handoff/` (per-route specs), and `sessions/<slug>/` for multi-session work. The project-root `DESIGN.md` lives alongside `package.json` — it is the visual-identity source of truth, not a per-tool artifact. Add `.mockup-gallery/` to `.gitignore` to keep review state out of version control.

## Tech stack

Node.js's built-in `http` module runs the whole server — `package.json` declares zero `dependencies` and zero `devDependencies`, so there's no Express, no bundler, no framework in the request path. The frontend is one vanilla-JS file (`gallery/gallery.html`) serving mockups in an iframe. Storage is flat JSON (`selections.json`, `selected.json`, `implemented.json`, `accepted-designs.json`, `state.json`) plus Markdown (`handoff/*.md`, root `DESIGN.md`) — no database. Route detection is a from-scratch filesystem/regex scanner covering Next.js App Router, Next.js Pages Router, Astro, SwiftUI, Python (Flask/FastAPI-style decorators), Wouter/React Router SPAs, static HTML, and a manual JSON manifest fallback. Distribution and host-agent integration run through the Claude Code and Codex plugin surfaces (`hooks/*.mjs`, `commands/`, `skills/mockup-review`) — no separate model or inference dependency; the host agent's own model does the judgment work the gallery scaffolds for.

## Links

- [HTML project page](https://rosslabs.ai/projects/mockup-gallery/)
- [Source code](https://github.com/tyroneross/mockup-gallery)
