Browse Source

feat: add visual companion for brainstorming skill

Adds browser-based mockup display to replace ASCII art during
brainstorming sessions. Key components:

- Frame template with OS-aware light/dark theming
- CSS helpers for options, cards, mockups, split views
- Server lifecycle scripts (start/stop with random high port)
- Event watcher using tail+grep for feedback loop
- Claude instructions for using the visual companion

The skill now asks users if they want browser mockups and only
runs in Claude Code environments.
Jesse Vincent 7 months ago
parent
commit
ac3af07af0

+ 199 - 0
lib/brainstorm-server/CLAUDE-INSTRUCTIONS.md

@@ -0,0 +1,199 @@
+# Visual Companion Instructions for Claude
+
+This document explains how to use the brainstorm visual companion to show mockups, designs, and options to users without resorting to ASCII art.
+
+## When to Use
+
+Use the visual companion when you need to show:
+- **UI mockups** - layouts, navigation patterns, component designs
+- **Design comparisons** - "Which of these 3 approaches works better?"
+- **Interactive prototypes** - clickable wireframes
+- **Visual choices** - anything where seeing beats describing
+
+**Don't use it for:** simple text questions, code review, or when the user prefers terminal-only interaction.
+
+## Lifecycle
+
+```bash
+# Start server (returns JSON with URL)
+${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/start-server.sh
+
+# Tell user to open the URL in their browser
+
+# Write screens to /tmp/brainstorm/screen.html (auto-refreshes)
+
+# Wait for user feedback
+${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/wait-for-event.sh /path/to/server.log
+
+# When done, stop server
+${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/stop-server.sh
+```
+
+## Writing Screens
+
+Copy the frame template structure but replace `#claude-content` with your content:
+
+```html
+<div id="claude-content">
+  <h2>Your Question</h2>
+  <p class="subtitle">Brief context</p>
+
+  <!-- Your content here -->
+</div>
+```
+
+The frame template (`frame-template.html`) includes CSS for:
+- OS-aware light/dark theming
+- Fixed header and feedback footer
+- Common UI patterns (see below)
+
+## CSS Helper Classes
+
+### Options (A/B/C choices)
+
+```html
+<div class="options">
+  <div class="option" data-choice="a" onclick="toggleSelect(this)">
+    <div class="letter">A</div>
+    <div class="content">
+      <h3>Option Title</h3>
+      <p>Description of this option</p>
+    </div>
+  </div>
+  <!-- More options... -->
+</div>
+```
+
+### Cards (visual designs)
+
+```html
+<div class="cards">
+  <div class="card" data-choice="design1" onclick="toggleSelect(this)">
+    <div class="card-image">
+      <!-- Put mockup content here -->
+    </div>
+    <div class="card-body">
+      <h3>Design Name</h3>
+      <p>Brief description</p>
+    </div>
+  </div>
+</div>
+```
+
+### Mockup Container
+
+```html
+<div class="mockup">
+  <div class="mockup-header">Preview: Dashboard Layout</div>
+  <div class="mockup-body">
+    <!-- Your mockup HTML -->
+  </div>
+</div>
+```
+
+### Split View (side-by-side)
+
+```html
+<div class="split">
+  <div class="mockup"><!-- Left side --></div>
+  <div class="mockup"><!-- Right side --></div>
+</div>
+```
+
+### Pros/Cons
+
+```html
+<div class="pros-cons">
+  <div class="pros">
+    <h4>Pros</h4>
+    <ul>
+      <li>Benefit one</li>
+      <li>Benefit two</li>
+    </ul>
+  </div>
+  <div class="cons">
+    <h4>Cons</h4>
+    <ul>
+      <li>Drawback one</li>
+      <li>Drawback two</li>
+    </ul>
+  </div>
+</div>
+```
+
+### Inline Mockup Elements
+
+```html
+<div class="mock-nav">Logo | Home | About | Contact</div>
+<div style="display: flex;">
+  <div class="mock-sidebar">Navigation</div>
+  <div class="mock-content">Main content area</div>
+</div>
+<button class="mock-button">Action Button</button>
+<input class="mock-input" placeholder="Input field">
+```
+
+## User Feedback
+
+When the user clicks Send, you receive JSON like:
+
+```json
+{"choice": "a", "feedback": "I like this but make the header smaller"}
+```
+
+- `choice` - which option/card they selected (from `data-choice` attribute)
+- `feedback` - any notes they typed
+
+## Example: Design Comparison
+
+```html
+<div id="claude-content">
+  <h2>Which blog layout works better?</h2>
+  <p class="subtitle">Consider readability and visual hierarchy</p>
+
+  <div class="cards">
+    <div class="card" data-choice="classic" onclick="toggleSelect(this)">
+      <div class="card-image">
+        <div style="padding: 1rem;">
+          <div class="mock-nav">Blog Title</div>
+          <div style="padding: 1rem;">
+            <h3 style="margin-bottom: 0.5rem;">Post Title</h3>
+            <p style="color: var(--text-secondary); font-size: 0.9rem;">
+              Content preview text goes here...
+            </p>
+          </div>
+        </div>
+      </div>
+      <div class="card-body">
+        <h3>Classic Layout</h3>
+        <p>Traditional blog with posts in a single column</p>
+      </div>
+    </div>
+
+    <div class="card" data-choice="magazine" onclick="toggleSelect(this)">
+      <div class="card-image">
+        <div style="padding: 1rem;">
+          <div class="mock-nav">Blog Title</div>
+          <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; padding: 0.5rem;">
+            <div class="placeholder" style="padding: 1rem;">Featured</div>
+            <div class="placeholder" style="padding: 0.5rem;">Post</div>
+          </div>
+        </div>
+      </div>
+      <div class="card-body">
+        <h3>Magazine Layout</h3>
+        <p>Grid-based with featured posts</p>
+      </div>
+    </div>
+  </div>
+</div>
+```
+
+## Tips
+
+1. **Keep mockups simple** - Focus on layout and structure, not pixel-perfect design
+2. **Use placeholders** - The `.placeholder` class works great for content areas
+3. **Label clearly** - Use `.mockup-header` to explain what each mockup shows
+4. **Limit choices** - 2-4 options is ideal; more gets overwhelming
+5. **Provide context** - Use `.subtitle` to explain what you're asking
+6. **Regenerate fully** - Write the complete HTML each turn; don't try to patch

+ 259 - 0
lib/brainstorm-server/frame-template.html

@@ -0,0 +1,259 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <title>Brainstorm Companion</title>
+  <style>
+    /*
+     * BRAINSTORM COMPANION FRAME TEMPLATE
+     *
+     * This template provides a consistent frame with:
+     * - OS-aware light/dark theming
+     * - Fixed header and feedback footer
+     * - Scrollable main content area
+     * - CSS helpers for common UI patterns
+     *
+     * CLAUDE: Replace the contents of #claude-content with your content.
+     * Keep the header, main wrapper, and feedback-footer intact.
+     */
+
+    * { box-sizing: border-box; margin: 0; padding: 0; }
+    html, body { height: 100%; overflow: hidden; }
+
+    /* ===== THEME VARIABLES ===== */
+    :root {
+      --bg-primary: #f5f5f7;
+      --bg-secondary: #ffffff;
+      --bg-tertiary: #e5e5e7;
+      --border: #d1d1d6;
+      --text-primary: #1d1d1f;
+      --text-secondary: #86868b;
+      --text-tertiary: #aeaeb2;
+      --accent: #0071e3;
+      --accent-hover: #0077ed;
+      --success: #34c759;
+      --warning: #ff9f0a;
+      --error: #ff3b30;
+      --selected-bg: #e8f4fd;
+      --selected-border: #0071e3;
+    }
+
+    @media (prefers-color-scheme: dark) {
+      :root {
+        --bg-primary: #1d1d1f;
+        --bg-secondary: #2d2d2f;
+        --bg-tertiary: #3d3d3f;
+        --border: #424245;
+        --text-primary: #f5f5f7;
+        --text-secondary: #86868b;
+        --text-tertiary: #636366;
+        --accent: #0a84ff;
+        --accent-hover: #409cff;
+        --selected-bg: rgba(10, 132, 255, 0.15);
+        --selected-border: #0a84ff;
+      }
+    }
+
+    body {
+      font-family: system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
+      background: var(--bg-primary);
+      color: var(--text-primary);
+      display: flex;
+      flex-direction: column;
+      line-height: 1.5;
+    }
+
+    /* ===== FRAME STRUCTURE ===== */
+    .header {
+      background: var(--bg-secondary);
+      padding: 0.5rem 1.5rem;
+      display: flex;
+      justify-content: space-between;
+      align-items: center;
+      border-bottom: 1px solid var(--border);
+      flex-shrink: 0;
+    }
+    .header h1 { font-size: 0.85rem; font-weight: 500; color: var(--text-secondary); }
+    .header .status { font-size: 0.7rem; color: var(--success); display: flex; align-items: center; gap: 0.4rem; }
+    .header .status::before { content: ''; width: 6px; height: 6px; background: var(--success); border-radius: 50%; }
+
+    .main { flex: 1; overflow-y: auto; }
+    #claude-content { padding: 2rem; min-height: 100%; }
+
+    .feedback-footer {
+      background: var(--bg-secondary);
+      border-top: 1px solid var(--border);
+      padding: 0.75rem 1.5rem;
+      flex-shrink: 0;
+    }
+    .feedback-footer label { display: block; font-size: 0.65rem; color: var(--text-secondary); margin-bottom: 0.4rem; text-transform: uppercase; letter-spacing: 0.05em; }
+    .feedback-row { display: flex; gap: 0.5rem; }
+    .feedback-footer textarea {
+      flex: 1;
+      background: var(--bg-primary);
+      border: 1px solid var(--border);
+      border-radius: 6px;
+      padding: 0.5rem 0.75rem;
+      color: var(--text-primary);
+      font-family: inherit;
+      font-size: 0.85rem;
+      resize: none;
+      height: 36px;
+    }
+    .feedback-footer textarea:focus { outline: none; border-color: var(--accent); }
+    .feedback-footer button {
+      background: var(--accent);
+      color: white;
+      border: none;
+      padding: 0 1rem;
+      border-radius: 6px;
+      font-size: 0.8rem;
+      cursor: pointer;
+    }
+    .feedback-footer button:hover { background: var(--accent-hover); }
+
+    /* ===== TYPOGRAPHY ===== */
+    h2 { font-size: 1.5rem; font-weight: 600; margin-bottom: 0.5rem; }
+    h3 { font-size: 1.1rem; font-weight: 600; margin-bottom: 0.25rem; }
+    .subtitle { color: var(--text-secondary); margin-bottom: 1.5rem; }
+    .section { margin-bottom: 2rem; }
+    .label { font-size: 0.7rem; color: var(--text-secondary); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
+
+    /* ===== OPTIONS (for A/B/C choices) ===== */
+    .options { display: flex; flex-direction: column; gap: 0.75rem; }
+    .option {
+      background: var(--bg-secondary);
+      border: 2px solid var(--border);
+      border-radius: 12px;
+      padding: 1rem 1.25rem;
+      cursor: pointer;
+      transition: all 0.15s ease;
+      display: flex;
+      align-items: flex-start;
+      gap: 1rem;
+    }
+    .option:hover { border-color: var(--accent); }
+    .option.selected { background: var(--selected-bg); border-color: var(--selected-border); }
+    .option .letter {
+      background: var(--bg-tertiary);
+      color: var(--text-secondary);
+      width: 1.75rem; height: 1.75rem;
+      border-radius: 6px;
+      display: flex; align-items: center; justify-content: center;
+      font-weight: 600; font-size: 0.85rem; flex-shrink: 0;
+    }
+    .option.selected .letter { background: var(--accent); color: white; }
+    .option .content { flex: 1; }
+    .option .content h3 { font-size: 0.95rem; margin-bottom: 0.15rem; }
+    .option .content p { color: var(--text-secondary); font-size: 0.85rem; margin: 0; }
+
+    /* ===== CARDS (for showing designs/mockups) ===== */
+    .cards { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1rem; }
+    .card {
+      background: var(--bg-secondary);
+      border: 1px solid var(--border);
+      border-radius: 12px;
+      overflow: hidden;
+      cursor: pointer;
+      transition: all 0.15s ease;
+    }
+    .card:hover { border-color: var(--accent); transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0,0,0,0.1); }
+    .card.selected { border-color: var(--selected-border); border-width: 2px; }
+    .card-image { background: var(--bg-tertiary); aspect-ratio: 16/10; display: flex; align-items: center; justify-content: center; }
+    .card-body { padding: 1rem; }
+    .card-body h3 { margin-bottom: 0.25rem; }
+    .card-body p { color: var(--text-secondary); font-size: 0.85rem; }
+
+    /* ===== MOCKUP CONTAINER ===== */
+    .mockup {
+      background: var(--bg-secondary);
+      border: 1px solid var(--border);
+      border-radius: 12px;
+      overflow: hidden;
+      margin-bottom: 1.5rem;
+    }
+    .mockup-header {
+      background: var(--bg-tertiary);
+      padding: 0.5rem 1rem;
+      font-size: 0.75rem;
+      color: var(--text-secondary);
+      border-bottom: 1px solid var(--border);
+    }
+    .mockup-body { padding: 1.5rem; }
+
+    /* ===== SPLIT VIEW (side-by-side comparison) ===== */
+    .split { display: grid; grid-template-columns: 1fr 1fr; gap: 1.5rem; }
+    @media (max-width: 700px) { .split { grid-template-columns: 1fr; } }
+
+    /* ===== PROS/CONS ===== */
+    .pros-cons { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin: 1rem 0; }
+    .pros, .cons { background: var(--bg-secondary); border-radius: 8px; padding: 1rem; }
+    .pros h4 { color: var(--success); font-size: 0.85rem; margin-bottom: 0.5rem; }
+    .cons h4 { color: var(--error); font-size: 0.85rem; margin-bottom: 0.5rem; }
+    .pros ul, .cons ul { margin-left: 1.25rem; font-size: 0.85rem; color: var(--text-secondary); }
+    .pros li, .cons li { margin-bottom: 0.25rem; }
+
+    /* ===== PLACEHOLDER (for mockup areas) ===== */
+    .placeholder {
+      background: var(--bg-tertiary);
+      border: 2px dashed var(--border);
+      border-radius: 8px;
+      padding: 2rem;
+      text-align: center;
+      color: var(--text-tertiary);
+    }
+
+    /* ===== INLINE MOCKUP ELEMENTS ===== */
+    .mock-nav { background: var(--accent); color: white; padding: 0.75rem 1rem; display: flex; gap: 1.5rem; font-size: 0.9rem; }
+    .mock-sidebar { background: var(--bg-tertiary); padding: 1rem; min-width: 180px; }
+    .mock-content { padding: 1.5rem; flex: 1; }
+    .mock-button { background: var(--accent); color: white; border: none; padding: 0.5rem 1rem; border-radius: 6px; font-size: 0.85rem; }
+    .mock-input { background: var(--bg-primary); border: 1px solid var(--border); border-radius: 6px; padding: 0.5rem; width: 100%; }
+  </style>
+</head>
+<body>
+  <div class="header">
+    <h1>Brainstorm Companion</h1>
+    <div class="status">Connected</div>
+  </div>
+
+  <div class="main">
+    <div id="claude-content">
+      <!-- CLAUDE: Replace this content -->
+      <h2>Visual Brainstorming</h2>
+      <p class="subtitle">Claude will show mockups and options here.</p>
+    </div>
+  </div>
+
+  <div class="feedback-footer">
+    <label>Feedback for Claude</label>
+    <div class="feedback-row">
+      <textarea id="feedback" placeholder="Add notes (optional)..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();send()}"></textarea>
+      <button onclick="send()">Send</button>
+    </div>
+  </div>
+
+  <script>
+    let selectedChoice = null;
+
+    function toggleSelect(el) {
+      const container = el.closest('.options') || el.closest('.cards');
+      if (container) {
+        container.querySelectorAll('.option, .card').forEach(o => o.classList.remove('selected'));
+      }
+      el.classList.add('selected');
+      selectedChoice = el.dataset.choice;
+    }
+
+    function send() {
+      const feedbackEl = document.getElementById('feedback');
+      const feedback = feedbackEl.value.trim();
+      const payload = {};
+      if (selectedChoice) payload.choice = selectedChoice;
+      if (feedback) payload.feedback = feedback;
+      if (Object.keys(payload).length === 0) return;
+      brainstorm.sendToClaude(payload);
+      feedbackEl.value = '';
+    }
+  </script>
+</body>
+</html>

+ 2 - 1
lib/brainstorm-server/index.js

@@ -5,7 +5,8 @@ const chokidar = require('chokidar');
 const fs = require('fs');
 const path = require('path');
 
-const PORT = process.env.BRAINSTORM_PORT || 3333;
+// Use provided port or pick a random high port (49152-65535)
+const PORT = process.env.BRAINSTORM_PORT || (49152 + Math.floor(Math.random() * 16383));
 const SCREEN_FILE = process.env.BRAINSTORM_SCREEN || '/tmp/brainstorm/screen.html';
 const SCREEN_DIR = path.dirname(SCREEN_FILE);
 

+ 42 - 0
lib/brainstorm-server/start-server.sh

@@ -0,0 +1,42 @@
+#!/bin/bash
+# Start the brainstorm server and output connection info
+# Usage: start-server.sh
+#
+# Starts server on a random high port, outputs JSON with URL
+# Server runs in background, PID saved for cleanup
+
+SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
+SCREEN_DIR="${BRAINSTORM_SCREEN_DIR:-/tmp/brainstorm}"
+SCREEN_FILE="${SCREEN_DIR}/screen.html"
+PID_FILE="${SCREEN_DIR}/.server.pid"
+LOG_FILE="${SCREEN_DIR}/.server.log"
+
+# Ensure screen directory exists
+mkdir -p "$SCREEN_DIR"
+
+# Kill any existing server
+if [[ -f "$PID_FILE" ]]; then
+  old_pid=$(cat "$PID_FILE")
+  kill "$old_pid" 2>/dev/null
+  rm -f "$PID_FILE"
+fi
+
+# Start server, capturing output to log file
+cd "$SCRIPT_DIR"
+node index.js > "$LOG_FILE" 2>&1 &
+SERVER_PID=$!
+echo "$SERVER_PID" > "$PID_FILE"
+
+# Wait for server-started message (check log file)
+for i in {1..50}; do
+  if grep -q "server-started" "$LOG_FILE" 2>/dev/null; then
+    # Extract and output the server-started line
+    grep "server-started" "$LOG_FILE" | head -1
+    exit 0
+  fi
+  sleep 0.1
+done
+
+# Timeout - server didn't start
+echo '{"error": "Server failed to start within 5 seconds"}'
+exit 1

+ 15 - 0
lib/brainstorm-server/stop-server.sh

@@ -0,0 +1,15 @@
+#!/bin/bash
+# Stop the brainstorm server and clean up
+# Usage: stop-server.sh
+
+SCREEN_DIR="${BRAINSTORM_SCREEN_DIR:-/tmp/brainstorm}"
+PID_FILE="${SCREEN_DIR}/.server.pid"
+
+if [[ -f "$PID_FILE" ]]; then
+  pid=$(cat "$PID_FILE")
+  kill "$pid" 2>/dev/null
+  rm -f "$PID_FILE"
+  echo '{"status": "stopped"}'
+else
+  echo '{"status": "not_running"}'
+fi

+ 64 - 14
skills/brainstorming/SKILL.md

@@ -95,22 +95,72 @@ digraph brainstorming {
 - **Incremental validation** - Present design, get approval before moving on
 - **Be flexible** - Go back and clarify when something doesn't make sense
 
-## Visual Companion (Optional)
+## Visual Companion (Claude Code Only)
 
-When brainstorming involves visual elements - UI mockups, wireframes, interactive prototypes - use the browser-based visual companion.
+When brainstorming involves visual elements - UI mockups, layouts, design comparisons - you can use a browser-based visual companion instead of ASCII art. **This only works in Claude Code.**
 
-**When to use:**
-- Presenting UI/UX options that benefit from visual comparison
-- Showing wireframes or layout options
-- Gathering structured feedback (ratings, forms)
-- Prototyping click interactions
+### When to Offer
 
-**How it works:**
-1. Start the server as a background job
-2. Tell user to open http://localhost:3333
-3. Write HTML to `/tmp/brainstorm/screen.html` (auto-refreshes)
-4. Check background task output for user interactions
+If the brainstorm involves visual decisions (UI layouts, design choices, mockups), ask the user:
 
-The terminal remains the primary conversation interface. The browser is a visual aid.
+> "This involves some visual decisions. Would you like me to show mockups in a browser window? (Requires opening a local URL)"
 
-**Reference:** See `visual-companion.md` in this skill directory for HTML patterns and API details.
+Only proceed with visual companion if they agree. Otherwise, describe options in text.
+
+### Starting the Visual Companion
+
+```bash
+# Start server (outputs JSON with URL)
+${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/start-server.sh
+
+# Output looks like: {"type":"server-started","port":52341,"url":"http://localhost:52341"}
+```
+
+Tell the user to open the URL in their browser.
+
+### Showing Content
+
+Write complete HTML to `/tmp/brainstorm/screen.html`. The browser auto-refreshes.
+
+Use the frame template structure from `${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/frame-template.html`:
+- Keep the header and feedback-footer intact
+- Replace `#claude-content` with your content
+- Use the CSS helper classes (`.options`, `.cards`, `.mockup`, `.split`, `.pros-cons`)
+
+See `${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/CLAUDE-INSTRUCTIONS.md` for detailed examples.
+
+### Waiting for User Feedback
+
+Run the watcher as a background bash command:
+
+```bash
+${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/wait-for-event.sh /tmp/brainstorm/.server.log
+```
+
+When the user clicks Send in the browser, the watcher exits and you receive their feedback as JSON:
+```json
+{"choice": "a", "feedback": "I like this but make the header smaller"}
+```
+
+### The Loop
+
+1. Write screen HTML
+2. Start watcher (background bash)
+3. Watcher completes when user sends feedback
+4. Read feedback, respond with new screen
+5. Repeat until done
+
+### Cleaning Up
+
+When the visual brainstorming session is complete:
+
+```bash
+${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/stop-server.sh
+```
+
+### Tips
+
+- **Keep mockups simple** - Focus on layout and structure, not pixel-perfect design
+- **Limit choices** - 2-4 options is ideal
+- **Regenerate fully** - Write complete HTML each turn; the screen is stateless
+- **Terminal is primary** - The browser shows things; conversation happens in terminal

+ 78 - 43
skills/brainstorming/visual-companion.md

@@ -1,73 +1,108 @@
 # Visual Companion Reference
 
-## Starting the Server
+Quick reference for using the visual brainstorming companion.
 
-Run as a background job:
+## Files
 
-```bash
-node ${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/index.js
-```
+| File | Purpose |
+|------|---------|
+| `lib/brainstorm-server/start-server.sh` | Start server, outputs JSON with URL |
+| `lib/brainstorm-server/stop-server.sh` | Stop server and clean up |
+| `lib/brainstorm-server/wait-for-event.sh` | Wait for user feedback |
+| `lib/brainstorm-server/frame-template.html` | Base HTML template with CSS |
+| `lib/brainstorm-server/CLAUDE-INSTRUCTIONS.md` | Detailed usage guide |
+| `/tmp/brainstorm/screen.html` | Write your screens here |
+| `/tmp/brainstorm/.server.log` | Server output (for watcher) |
 
-Tell the user: "I've started a visual companion at http://localhost:3333 - open it in a browser."
+## Quick Start
 
-## Pushing Screens
+```bash
+# 1. Start server
+${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/start-server.sh
+# Returns: {"type":"server-started","port":52341,"url":"http://localhost:52341"}
 
-Write HTML to `/tmp/brainstorm/screen.html`. The server watches this file and auto-refreshes the browser.
+# 2. Write screen
+# Write HTML to /tmp/brainstorm/screen.html
 
-## Reading User Responses
+# 3. Wait for feedback (background)
+${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/wait-for-event.sh /tmp/brainstorm/.server.log
 
-Check the background task output for JSON events:
+# 4. Read watcher output when it completes
+# Returns: {"choice":"a","feedback":"user notes"}
 
-```json
-{"source":"user-event","type":"click","text":"Option A","choice":"optionA","timestamp":1234567890}
-{"source":"user-event","type":"submit","data":{"notes":"My feedback"},"timestamp":1234567891}
+# 5. Clean up when done
+${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/stop-server.sh
 ```
 
-Event types:
-- **click**: User clicked button or `data-choice` element
-- **submit**: User submitted form (includes all form data)
-- **input**: User typed in field (debounced 500ms)
-- **choice**: Explicit choice via `brainstorm.choice()` call
-
-## HTML Patterns
-
-### Choice Cards
+## CSS Classes
 
+### Options (A/B/C choices)
 ```html
 <div class="options">
-  <button data-choice="optionA">
-    <h3>Option A</h3>
-    <p>Description</p>
-  </button>
-  <button data-choice="optionB">
-    <h3>Option B</h3>
-    <p>Description</p>
-  </button>
+  <div class="option" data-choice="a" onclick="toggleSelect(this)">
+    <div class="letter">A</div>
+    <div class="content">
+      <h3>Title</h3>
+      <p>Description</p>
+    </div>
+  </div>
 </div>
 ```
 
-### Interactive Mockup
+### Cards (visual designs)
+```html
+<div class="cards">
+  <div class="card" data-choice="x" onclick="toggleSelect(this)">
+    <div class="card-image"><!-- mockup --></div>
+    <div class="card-body">
+      <h3>Name</h3>
+      <p>Description</p>
+    </div>
+  </div>
+</div>
+```
 
+### Mockup container
 ```html
 <div class="mockup">
-  <header data-choice="header">App Header</header>
-  <nav data-choice="nav">Navigation</nav>
-  <main data-choice="main">Content</main>
+  <div class="mockup-header">Label</div>
+  <div class="mockup-body"><!-- content --></div>
 </div>
 ```
 
-### Form with Notes
-
+### Split view
 ```html
-<form>
-  <label>Priority: <input type="range" name="priority" min="1" max="5"></label>
-  <textarea name="notes" placeholder="Additional thoughts..."></textarea>
-  <button type="submit">Submit</button>
-</form>
+<div class="split">
+  <div><!-- left --></div>
+  <div><!-- right --></div>
+</div>
 ```
 
-### Explicit JavaScript
+### Pros/Cons
+```html
+<div class="pros-cons">
+  <div class="pros"><h4>Pros</h4><ul><li>...</li></ul></div>
+  <div class="cons"><h4>Cons</h4><ul><li>...</li></ul></div>
+</div>
+```
 
+### Mock elements
 ```html
-<button onclick="brainstorm.choice('custom', {extra: 'data'})">Custom</button>
+<div class="mock-nav">Nav items</div>
+<div class="mock-sidebar">Sidebar</div>
+<div class="mock-content">Content</div>
+<button class="mock-button">Button</button>
+<input class="mock-input" placeholder="Input">
+<div class="placeholder">Placeholder area</div>
 ```
+
+## User Feedback Format
+
+```json
+{
+  "choice": "option-id",    // from data-choice attribute
+  "feedback": "user notes"  // from feedback textarea
+}
+```
+
+Both fields are optional - user may select without notes, or send notes without selection.