# Visual Brainstorming Companion Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** Give Claude a browser-based visual companion for brainstorming sessions - show mockups, prototypes, and interactive choices alongside terminal conversation. **Architecture:** Claude writes HTML to a temp file. A local Node.js server watches that file and serves it with an auto-injected helper library. User interactions flow via WebSocket to server stdout, which Claude sees in background task output. **Tech Stack:** Node.js, Express, ws (WebSocket), chokidar (file watching) --- ## Task 1: Create the Server Foundation **Files:** - Create: `lib/brainstorm-server/index.js` - Create: `lib/brainstorm-server/package.json` **Step 1: Create package.json** ```json { "name": "brainstorm-server", "version": "1.0.0", "description": "Visual brainstorming companion server for Claude Code", "main": "index.js", "dependencies": { "chokidar": "^3.5.3", "express": "^4.18.2", "ws": "^8.14.2" } } ``` **Step 2: Create minimal server that starts** ```javascript const express = require('express'); const http = require('http'); const WebSocket = require('ws'); const chokidar = require('chokidar'); const fs = require('fs'); const path = require('path'); const PORT = process.env.BRAINSTORM_PORT || 3333; const SCREEN_FILE = process.env.BRAINSTORM_SCREEN || '/tmp/brainstorm/screen.html'; const SCREEN_DIR = path.dirname(SCREEN_FILE); // Ensure screen directory exists if (!fs.existsSync(SCREEN_DIR)) { fs.mkdirSync(SCREEN_DIR, { recursive: true }); } // Create default screen if none exists if (!fs.existsSync(SCREEN_FILE)) { fs.writeFileSync(SCREEN_FILE, ` Brainstorm Companion

Brainstorm Companion

Waiting for Claude to push a screen...

`); } const app = express(); const server = http.createServer(app); const wss = new WebSocket.Server({ server }); // Track connected browsers for reload notifications const clients = new Set(); wss.on('connection', (ws) => { clients.add(ws); ws.on('close', () => clients.delete(ws)); ws.on('message', (data) => { // User interaction event - write to stdout for Claude const event = JSON.parse(data.toString()); console.log(JSON.stringify({ type: 'user-event', ...event })); }); }); // Serve current screen with helper.js injected app.get('/', (req, res) => { let html = fs.readFileSync(SCREEN_FILE, 'utf-8'); // Inject helper script before const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8'); const injection = ``; if (html.includes('')) { html = html.replace('', `${injection}\n`); } else { html += injection; } res.type('html').send(html); }); // Watch for screen file changes chokidar.watch(SCREEN_FILE).on('change', () => { console.log(JSON.stringify({ type: 'screen-updated', file: SCREEN_FILE })); // Notify all browsers to reload clients.forEach(ws => { if (ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'reload' })); } }); }); server.listen(PORT, '127.0.0.1', () => { console.log(JSON.stringify({ type: 'server-started', port: PORT, url: `http://localhost:${PORT}` })); }); ``` **Step 3: Run npm install** Run: `cd lib/brainstorm-server && npm install` Expected: Dependencies installed **Step 4: Test server starts** Run: `cd lib/brainstorm-server && timeout 3 node index.js || true` Expected: See JSON with `server-started` and port info **Step 5: Commit** ```bash git add lib/brainstorm-server/ git commit -m "feat: add brainstorm server foundation" ``` --- ## Task 2: Create the Helper Library **Files:** - Create: `lib/brainstorm-server/helper.js` **Step 1: Create helper.js with event auto-capture** ```javascript (function() { const WS_URL = 'ws://' + window.location.host; let ws = null; let eventQueue = []; function connect() { ws = new WebSocket(WS_URL); ws.onopen = () => { // Send any queued events eventQueue.forEach(e => ws.send(JSON.stringify(e))); eventQueue = []; }; ws.onmessage = (msg) => { const data = JSON.parse(msg.data); if (data.type === 'reload') { window.location.reload(); } }; ws.onclose = () => { // Reconnect after 1 second setTimeout(connect, 1000); }; } function send(event) { event.timestamp = Date.now(); if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify(event)); } else { eventQueue.push(event); } } // Auto-capture clicks on interactive elements document.addEventListener('click', (e) => { const target = e.target.closest('button, a, [data-choice], [role="button"], input[type="submit"]'); if (!target) return; // Don't capture regular link navigation if (target.tagName === 'A' && !target.dataset.choice) return; e.preventDefault(); send({ type: 'click', text: target.textContent.trim(), choice: target.dataset.choice || null, id: target.id || null, className: target.className || null }); }); // Auto-capture form submissions document.addEventListener('submit', (e) => { e.preventDefault(); const form = e.target; const formData = new FormData(form); const data = {}; formData.forEach((value, key) => { data[key] = value; }); send({ type: 'submit', formId: form.id || null, formName: form.name || null, data: data }); }); // Auto-capture input changes (debounced) let inputTimeout = null; document.addEventListener('input', (e) => { const target = e.target; if (!target.matches('input, textarea, select')) return; clearTimeout(inputTimeout); inputTimeout = setTimeout(() => { send({ type: 'input', name: target.name || null, id: target.id || null, value: target.value, inputType: target.type || target.tagName.toLowerCase() }); }, 500); // 500ms debounce }); // Expose for explicit use if needed window.brainstorm = { send: send, choice: (value, metadata = {}) => send({ type: 'choice', value, ...metadata }) }; connect(); })(); ``` **Step 2: Verify helper.js is syntactically valid** Run: `node -c lib/brainstorm-server/helper.js` Expected: No syntax errors **Step 3: Commit** ```bash git add lib/brainstorm-server/helper.js git commit -m "feat: add browser helper library for event capture" ``` --- ## Task 3: Write Tests for the Server **Files:** - Create: `tests/brainstorm-server/server.test.js` - Create: `tests/brainstorm-server/package.json` **Step 1: Create test package.json** ```json { "name": "brainstorm-server-tests", "version": "1.0.0", "scripts": { "test": "node server.test.js" } } ``` **Step 2: Write server tests** ```javascript const { spawn } = require('child_process'); const http = require('http'); const WebSocket = require('ws'); const fs = require('fs'); const path = require('path'); const assert = require('assert'); const SERVER_PATH = path.join(__dirname, '../../lib/brainstorm-server/index.js'); const TEST_PORT = 3334; const TEST_SCREEN = '/tmp/brainstorm-test/screen.html'; // Clean up test directory function cleanup() { if (fs.existsSync(path.dirname(TEST_SCREEN))) { fs.rmSync(path.dirname(TEST_SCREEN), { recursive: true }); } } async function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function fetch(url) { return new Promise((resolve, reject) => { http.get(url, (res) => { let data = ''; res.on('data', chunk => data += chunk); res.on('end', () => resolve({ status: res.statusCode, body: data })); }).on('error', reject); }); } async function runTests() { cleanup(); // Start server const server = spawn('node', [SERVER_PATH], { env: { ...process.env, BRAINSTORM_PORT: TEST_PORT, BRAINSTORM_SCREEN: TEST_SCREEN } }); let stdout = ''; server.stdout.on('data', (data) => { stdout += data.toString(); }); server.stderr.on('data', (data) => { console.error('Server stderr:', data.toString()); }); await sleep(1000); // Wait for server to start try { // Test 1: Server starts and outputs JSON console.log('Test 1: Server startup message'); assert(stdout.includes('server-started'), 'Should output server-started'); assert(stdout.includes(TEST_PORT.toString()), 'Should include port'); console.log(' PASS'); // Test 2: GET / returns HTML with helper injected console.log('Test 2: Serves HTML with helper injected'); const res = await fetch(`http://localhost:${TEST_PORT}/`); assert.strictEqual(res.status, 200); assert(res.body.includes('brainstorm'), 'Should include brainstorm content'); assert(res.body.includes('WebSocket'), 'Should have helper.js injected'); console.log(' PASS'); // Test 3: WebSocket connection and event relay console.log('Test 3: WebSocket relays events to stdout'); stdout = ''; // Reset stdout capture const ws = new WebSocket(`ws://localhost:${TEST_PORT}`); await new Promise(resolve => ws.on('open', resolve)); ws.send(JSON.stringify({ type: 'click', text: 'Test Button' })); await sleep(100); assert(stdout.includes('user-event'), 'Should relay user events'); assert(stdout.includes('Test Button'), 'Should include event data'); ws.close(); console.log(' PASS'); // Test 4: File change triggers reload notification console.log('Test 4: File change notifies browsers'); const ws2 = new WebSocket(`ws://localhost:${TEST_PORT}`); await new Promise(resolve => ws2.on('open', resolve)); let gotReload = false; ws2.on('message', (data) => { const msg = JSON.parse(data.toString()); if (msg.type === 'reload') gotReload = true; }); // Modify the screen file fs.writeFileSync(TEST_SCREEN, 'Updated'); await sleep(500); assert(gotReload, 'Should send reload message on file change'); ws2.close(); console.log(' PASS'); console.log('\nAll tests passed!'); } finally { server.kill(); cleanup(); } } runTests().catch(err => { console.error('Test failed:', err); process.exit(1); }); ``` **Step 3: Run tests** Run: `cd tests/brainstorm-server && npm install ws && node server.test.js` Expected: All tests pass **Step 4: Commit** ```bash git add tests/brainstorm-server/ git commit -m "test: add brainstorm server integration tests" ``` --- ## Task 4: Add Visual Companion to Brainstorming Skill **Files:** - Modify: `skills/brainstorming/SKILL.md` - Create: `skills/brainstorming/visual-companion.md` (supporting doc) **Step 1: Create the supporting documentation** Create `skills/brainstorming/visual-companion.md`: ```markdown # Visual Companion Reference ## Starting the Server Run as a background job: ```bash node ${PLUGIN_ROOT}/lib/brainstorm-server/index.js ``` Tell the user: "I've started a visual companion at http://localhost:3333 - open it in a browser." ## Pushing Screens Write HTML to `/tmp/brainstorm/screen.html`. The server watches this file and auto-refreshes the browser. ## Reading User Responses Check the background task output for JSON events: ```json {"type":"user-event","type":"click","text":"Option A","choice":"optionA","timestamp":1234567890} {"type":"user-event","type":"submit","data":{"notes":"My feedback"},"timestamp":1234567891} ``` 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) ## HTML Patterns ### Choice Cards ```html
``` ### Interactive Mockup ```html
App Header
Content
``` ### Form with Notes ```html
``` ### Explicit JavaScript ```html ``` ``` **Step 2: Add visual companion section to brainstorming skill** Add after "Key Principles" in `skills/brainstorming/SKILL.md`: ```markdown ## Visual Companion (Optional) When brainstorming involves visual elements - UI mockups, wireframes, interactive prototypes - use the browser-based visual companion. **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 **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 The terminal remains the primary conversation interface. The browser is a visual aid. **Reference:** See `visual-companion.md` in this skill directory for HTML patterns and API details. ``` **Step 3: Verify the edits** Run: `grep -A5 "Visual Companion" skills/brainstorming/SKILL.md` Expected: Shows the new section **Step 4: Commit** ```bash git add skills/brainstorming/ git commit -m "feat: add visual companion to brainstorming skill" ``` --- ## Task 5: Add Server to Plugin Ignore (Optional Cleanup) **Files:** - Check if `.gitignore` needs node_modules exclusion for lib/brainstorm-server **Step 1: Check current gitignore** Run: `cat .gitignore 2>/dev/null || echo "No .gitignore"` **Step 2: Add node_modules if needed** If not already present, add: ``` lib/brainstorm-server/node_modules/ ``` **Step 3: Commit if changed** ```bash git add .gitignore git commit -m "chore: ignore brainstorm-server node_modules" ``` --- ## Summary After completing all tasks: 1. **Server** at `lib/brainstorm-server/` - Node.js server that watches HTML file and relays events 2. **Helper library** auto-injected - captures clicks, forms, inputs 3. **Tests** at `tests/brainstorm-server/` - verifies server behavior 4. **Brainstorming skill** updated with visual companion section and `visual-companion.md` reference doc **To use:** 1. Start server as background job: `node lib/brainstorm-server/index.js &` 2. Tell user to open `http://localhost:3333` 3. Write HTML to `/tmp/brainstorm/screen.html` 4. Check task output for user events