Design: gstack Visual Design Generation (`design` binary)
Generated by /office-hours on 2026-03-26 Branch: garrytan/agent-design-tools Repo: gstack Status: DRAFT Mode: Intrapreneurship
Overview
Design: gstack Visual Design Generation (design binary)
Generated by /office-hours on 2026-03-26 Branch: garrytan/agent-design-tools Repo: gstack Status: DRAFT Mode: Intrapreneurship
Context
gstack's design skills (/office-hours, /design-consultation, /plan-design-review, /design-review) all produce text descriptions of design — DESIGN.md files with hex codes, plan docs with pixel specs in prose, ASCII art wireframes. The creator is a designer who hand-designed HelloSign in OmniGraffle and finds this embarrassing.
The unit of value is wrong. Users don't need richer design language — they need an executable visual artifact that changes the conversation from "do you like this spec?" to "is this the screen?"
Problem Statement
Design skills describe design in text instead of showing it. The Argus UX overhaul plan is the example: 487 lines of detailed emotional arc specs, typography choices, animation timing — zero visual artifacts. An AI coding agent that "designs" should produce something you can look at and react to viscerally.
Demand Evidence
The creator/primary user finds the current output embarrassing. Every design skill session ends with prose where a mockup should be. GPT Image API now generates pixel-perfect UI mockups with accurate text rendering — the capability gap that justified text-only output no longer exists.
Narrowest Wedge
A compiled TypeScript binary (design/dist/design) that wraps the OpenAI Images/Responses API, callable from skill templates via $D (mirroring the existing $B browse binary pattern). Priority integration order: /office-hours → /plan-design-review → /design-consultation → /design-review.
Agreed Premises
- GPT Image API (via OpenAI Responses API) is the right engine. Google Stitch SDK is backup.
- Visual mockups are default-on for design skills with an easy skip path — not opt-in. (Revised per Codex challenge.)
- The integration is a shared utility (not per-skill reimplementation) — a
designbinary that any skill can call. - Priority: /office-hours first, then /plan-design-review, /design-consultation, /design-review.
Cross-Model Perspective (Codex)
Codex independently validated the core thesis: "The failure is not output quality within markdown; it is that the current unit of value is wrong." Key contributions:
- Challenged premise #2 (opt-in → default-on) — accepted
- Proposed vision-based quality gate: use GPT-4o vision to verify generated mockups for unreadable text, missing sections, broken layout, auto-retry once
- Scoped 48-hour prototype: shared
visual_mockup.tsutility, /office-hours + /plan-design-review only, hero mockup + 2 variants
Recommended Approach: design Binary (Approach B)
Architecture
Shares the browse binary's compilation and distribution pattern (bun build --compile, setup script, $VARIABLE resolution in skill templates) but is architecturally simpler — no persistent daemon server, no Chromium, no health checks, no token auth. The design binary is a stateless CLI that makes OpenAI API calls and writes PNGs to disk. Session state (for multi-turn iteration) is a JSON file.
New dependency: openai npm package (add to devDependencies, NOT runtime deps). Design binary compiled separately from browse so openai doesn't bloat the browse binary.
design/
├── src/
│ ├── cli.ts # Entry point, command dispatch
│ ├── commands.ts # Command registry (source of truth for docs + validation)
│ ├── generate.ts # Generate mockups from structured brief
│ ├── iterate.ts # Multi-turn iteration on existing mockups
│ ├── variants.ts # Generate N design variants from brief
│ ├── check.ts # Vision-based quality gate (GPT-4o)
│ ├── brief.ts # Structured brief type + assembly helpers
│ └── session.ts # Session state (response IDs for multi-turn)
├── dist/
│ ├── design # Compiled binary
│ └── .version # Git hash
└── test/
└── design.test.ts # Integration tests
Commands
# Generate a hero mockup from a structured brief
$D generate --brief "Dashboard for a coding assessment tool. Dark theme, cream accents. Shows: builder name, score badge, narrative letter, score cards. Target: technical users." --output /tmp/mockup-hero.png
# Generate 3 design variants
$D variants --brief "..." --count 3 --output-dir /tmp/mockups/
# Iterate on an existing mockup with feedback
$D iterate --session /tmp/design-session.json --feedback "Make the score cards larger, move the narrative above the scores" --output /tmp/mockup-v2.png
# Vision-based quality check (returns PASS/FAIL + issues)
$D check --image /tmp/mockup-hero.png --brief "Dashboard with builder name, score badge, narrative"
# One-shot with quality gate + auto-retry
$D generate --brief "..." --output /tmp/mockup.png --check --retry 1
# Pass a structured brief via JSON file
$D generate --brief-file /tmp/brief.json --output /tmp/mockup.png
# Generate comparison board HTML for user review
$D compare --images /tmp/mockups/variant-*.png --output /tmp/design-board.html
# Guided API key setup + smoke test
$D setup
Brief input modes:
--brief "plain text"— free-form text prompt (simple mode)--brief-file path.json— structured JSON matching theDesignBriefinterface (rich mode)- Skills construct a JSON brief file, write it to /tmp, and pass
--brief-file
All commands are registered in commands.ts including --check and --retry as flags on generate.
Design Exploration Workflow (from eng review)
The workflow is sequential, not parallel. PNGs are for visual exploration (human-facing), HTML wireframes are for implementation (agent-facing):
1. $D variants --brief "..." --count 3 --output-dir /tmp/mockups/
→ Generates 2-5 PNG mockup variations
2. $D compare --images /tmp/mockups/*.png --output /tmp/design-board.html
→ Generates HTML comparison board (spec below)
3. $B goto file:///tmp/design-board.html
→ User reviews all variants in headed Chrome
4. User picks favorite, rates, comments, clicks [Submit]
Agent polls: $B eval document.getElementById('status').textContent
Agent reads: $B eval document.getElementById('feedback-result').textContent
→ No clipboard, no pasting. Agent reads feedback directly from the page.
5. Claude generates HTML wireframe via DESIGN_SKETCH matching approved direction
→ Agent implements from the inspectable HTML, not the opaque PNG
Comparison Board Design Spec (from /plan-design-review)
Classifier: APP UI (task-focused, utility page). No product branding.
Layout: Single column, full-width mockups. Each variant gets the full viewport width for maximum image fidelity. Users scroll vertically through variants.
┌─────────────────────────────────────────────────────────────┐
│ HEADER BAR │
│ "Design Exploration" . project name . "3 variants" │
│ Mode indicator: [Wide exploration] | [Matching DESIGN.md] │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ VARIANT A (full width) │ │
│ │ [ mockup PNG, max-width: 1200px ] │ │
│ ├───────────────────────────────────────────────────────┤ │
│ │ (●) Pick ★★★★☆ [What do you like/dislike?____] │ │
│ │ [More like this] │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ VARIANT B (full width) │ │
│ │ [ mockup PNG, max-width: 1200px ] │ │
│ ├───────────────────────────────────────────────────────┤ │
│ │ ( ) Pick ★★★☆☆ [What do you like/dislike?____] │ │
│ │ [More like this] │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ... (scroll for more variants) │
│ │
│ ─── separator ───────────────────────────────────────── │
│ Overall direction (optional, collapsed by default) │
│ [textarea, 3 lines, expand on focus] │
│ │
│ ─── REGENERATE BAR (#f7f7f7 bg) ─────────────────────── │
│ "Want to explore more?" │
│ [Totally different] [Match my design] [Custom: ______] │
│ [Regenerate ->] │
│ ───────────────────────────────────────────────────────── │
│ [ ✓ Submit ] │
└─────────────────────────────────────────────────────────────┘
Visual spec:
- Background: #fff. No shadows, no card borders. Variant separation: 1px #e5e5e5 line.
- Typography: system font stack. Header: 16px semibold. Labels: 14px semibold. Feedback placeholder: 13px regular #999.
- Star rating: 5 clickable stars, filled=#000, unfilled=#ddd. Not colored, not animated.
- Radio button "Pick": explicit favorite selection. One per variant, mutually exclusive.
- "More like this" button: per-variant, triggers regeneration with that variant's style as seed.
- Submit button: #000 background, white text, right-aligned. Single CTA.
- Regenerate bar: #f7f7f7 background, visually distinct from feedback area.
- Max-width: 1200px centered for mockup images. Margins: 24px sides.
Interaction states:
- Loading (page opens before images ready): skeleton pulse with "Generating variant A..." per card. Stars/textarea/pick disabled.
- Partial failure (2 of 3 succeed): show good ones, error card for failed with per-variant [Retry].
- Post-submit: "Feedback submitted! Return to your coding agent." Page stays open.
- Regeneration: smooth transition, fade out old variants, skeleton pulses, fade in new. Scroll resets to top. Previous feedback cleared.
Feedback JSON structure (written to hidden #feedback-result element):
{
"preferred": "A",
"ratings": { "A": 4, "B": 3, "C": 2 },
"comments": {
"A": "Love the spacing, header feels right",
"B": "Too busy, but good color palette",
"C": "Wrong mood entirely"
},
"overall": "Go with A, make the CTA bigger",
"regenerated": false
}
Accessibility: Star ratings keyboard navigable (arrow keys). Textareas labeled ("Feedback for Variant A"). Submit/Regenerate keyboard accessible with visible focus ring. All text #333+ on white.
Responsive: >1200px: comfortable margins. 768-1200px: tighter margins. <768px: full-width, no horizontal scroll.
Screenshot consent (first-time only for $D evolve): "This will send a screenshot of your live site to OpenAI for design evolution. [Proceed] [Don't ask again]" Stored in ~/.gstack/config.yaml as design_screenshot_consent.
Why sequential: Codex adversarial review identified that raster PNGs are opaque to agents (no DOM, no states, no diffable structure). HTML wireframes preserve a bridge back to code. The PNG is for the human to say "yes, that's right." The HTML is for the agent to say "I know how to build this."
Key Design Decisions
1. Stateless CLI, not daemon
Browse needs a persistent Chromium instance. Design is just API calls — no reason for a server. Session state for multi-turn iteration is a JSON file written to /tmp/design-session-{id}.json containing previous_response_id.
- Session ID: generated from
${PID}-${timestamp}, passed via--sessionflag - Discovery: the
generatecommand creates the session file and prints its path;iteratereads it via--session - Cleanup: session files in /tmp are ephemeral (OS cleans up); no explicit cleanup needed
2. Structured brief input The brief is the interface between skill prose and image generation. Skills construct it from design context:
interface DesignBrief {
goal: string; // "Dashboard for coding assessment tool"
audience: string; // "Technical users, YC partners"
style: string; // "Dark theme, cream accents, minimal"
elements: string[]; // ["builder name", "score badge", "narrative letter"]
constraints?: string; // "Max width 1024px, mobile-first"
reference?: string; // Path to existing screenshot or DESIGN.md excerpt
screenType: string; // "desktop-dashboard" | "mobile-app" | "landing-page" | etc.
}
3. Default-on in design skills Skills generate mockups by default. The template includes skip language:
Generating visual mockup of the proposed design... (say "skip" if you don't need visuals)
4. Vision quality gate After generating, optionally pass the image through GPT-4o vision to check:
- Text readability (are labels/headings legible?)
- Layout completeness (are all requested elements present?)
- Visual coherence (does it look like a real UI, not a collage?) Auto-retry once on failure. If still fails, present anyway with a warning.
5. Output location: explorations in /tmp, approved finals in docs/designs/
- Exploration variants go to
/tmp/gstack-mockups-{session}/(ephemeral, not committed) - Only the user-approved final mockup gets saved to
docs/designs/(checked in) - Default output directory configurable via CLAUDE.md
design_output_dirsetting - Filename pattern:
{skill}-{description}-{timestamp}.png - Create
docs/designs/if it doesn't exist (mkdir -p) - Design doc references the committed image path
- Always show to user via the Read tool (which renders images inline in Claude Code)
- This avoids repo bloat: only approved designs are committed, not every exploration variant
- Fallback: if not in a git repo, save to
/tmp/gstack-mockup-{timestamp}.png
6. Trust boundary acknowledgment Default-on generation sends design brief text to OpenAI. This is a new external data flow vs. the existing HTML wireframe path which is entirely local. The brief contains only abstract design descriptions (goal, style, elements), never source code or user data. Screenshots from $B are NOT sent to OpenAI (the reference field in DesignBrief is a local file path used by the agent, not uploaded to the API). Document this in CLAUDE.md.
7. Rate limit mitigation
Variant generation uses staggered parallel: start each API call 1 second apart via Promise.allSettled() with delays. This avoids the 5-7 RPM rate limit on image generation while still being faster than fully serial. If any call 429s, retry with exponential backoff (2s, 4s, 8s).
Template Integration
Add to existing resolver: scripts/resolvers/design.ts (NOT a new file)
- Add
generateDesignSetup()for `{{DESI