Back to projects
Active Started Jan 2026

Mockup Gallery

Local design review for HTML mockups. Yay/nay ratings, page assignment, wireframe-first toggle, design-system and per-route handoff artifacts.

Node.js (built-in http, zero deps) Vanilla JS frontend Claude Code / Codex plugin hooks JSON + Markdown file storage Multi-framework route detection

Install

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

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.

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:

StageModel / tierWhere it runsWhy there
Gallery process itself (server, route detection, variant pairing, JSON/Markdown I/O)Noneserver/gallery-server.mjs, src/lib/*.mjsFully 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 commandsExplicit 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.
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 — 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 TypeDetectionRoute Source
Next.js App Routerapp/ dir with page.tsxFile-system routes
Astroastro.config.* present, then src/pages/pages.astro/.md/.mdx file routes
Next.js Pagespages/ dirFile-system routes
SwiftUI*View.swift filesView names + platform tags (ios/macos/watchos/shared)
Python (Flask/FastAPI)@app.route/@router.get decoratorsDecorated endpoints
Manual manifest.mockup-gallery/routes.jsonWhatever the project author lists
Wouter / React Router SPA<Route path=...> JSX in App.tsx/router.tsxParsed route props
StaticHTML 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.