Просмотр исходного кода

Use semantic filenames for visual companion screens

Server now watches directory for new .html files instead of a single
screen file. Claude writes to semantically named files like
platform.html, style.html, layout.html - each screen is a new file.

Benefits:
- No need to read before write (files are always new)
- Semantic filenames describe what's on screen
- History preserved in directory for debugging
- Server serves newest file by mtime automatically

Updated: index.js, start-server.sh, and all documentation.
Jesse Vincent 7 месяцев назад
Родитель
Сommit
e6d3c5ce15

+ 16 - 10
lib/brainstorm-server/CLAUDE-INSTRUCTIONS.md

@@ -15,27 +15,33 @@ Use the visual companion when you need to show:
 ## Lifecycle
 
 ```bash
-# Start server (returns JSON with URL and session paths)
+# Start server (returns JSON with URL and session directory)
 ${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/start-server.sh
 # Returns: {"type":"server-started","port":52341,"url":"http://localhost:52341",
-#           "screen_dir":"/tmp/brainstorm-12345-1234567890",
-#           "screen_file":"/tmp/brainstorm-12345-1234567890/screen.html"}
+#           "screen_dir":"/tmp/brainstorm-12345-1234567890"}
 
-# Save screen_dir and screen_file from response!
+# Save screen_dir from response!
 
 # Tell user to open the URL in their browser
 
-# Write screens to screen_file (auto-refreshes)
-
-# Wait for user feedback:
-# 1. Start watcher in background
-${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/wait-for-event.sh $SCREEN_DIR/.server.log
-# 2. Immediately call TaskOutput(task_id, block=true) to wait for completion
+# For each screen:
+# 1. Start watcher in background FIRST (avoids race condition)
+${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/wait-for-feedback.sh $SCREEN_DIR
+# 2. Write HTML to a NEW file in screen_dir (e.g., platform.html, style.html)
+#    Server automatically serves the newest file by modification time
+# 3. Call TaskOutput(task_id, block=true, timeout=600000) to wait for feedback
 
 # When done, stop server (pass screen_dir)
 ${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/stop-server.sh $SCREEN_DIR
 ```
 
+## File Naming
+
+- **Use semantic names**: `platform.html`, `visual-style.html`, `layout.html`, `controls.html`
+- **Never reuse filenames** - each screen must be a new file
+- **For iterations**: append version suffix like `layout-v2.html`, `layout-v3.html`
+- Server automatically serves the newest `.html` file by modification time
+
 ## Writing Screens
 
 Copy the frame template structure but replace `#claude-content` with your content:

+ 43 - 19
lib/brainstorm-server/index.js

@@ -7,17 +7,29 @@ const path = require('path');
 
 // 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);
+const SCREEN_DIR = process.env.BRAINSTORM_DIR || '/tmp/brainstorm';
 
 // 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, `<!DOCTYPE html>
+// Find the newest .html file in the directory by mtime
+function getNewestScreen() {
+  const files = fs.readdirSync(SCREEN_DIR)
+    .filter(f => f.endsWith('.html'))
+    .map(f => ({
+      name: f,
+      path: path.join(SCREEN_DIR, f),
+      mtime: fs.statSync(path.join(SCREEN_DIR, f)).mtime.getTime()
+    }))
+    .sort((a, b) => b.mtime - a.mtime);
+
+  return files.length > 0 ? files[0].path : null;
+}
+
+// Default waiting page (served when no screens exist yet)
+const WAITING_PAGE = `<!DOCTYPE html>
 <html>
 <head>
   <title>Brainstorm Companion</title>
@@ -31,8 +43,7 @@ if (!fs.existsSync(SCREEN_FILE)) {
   <h1>Brainstorm Companion</h1>
   <p>Waiting for Claude to push a screen...</p>
 </body>
-</html>`);
-}
+</html>`;
 
 const app = express();
 const server = http.createServer(app);
@@ -52,9 +63,10 @@ wss.on('connection', (ws) => {
   });
 });
 
-// Serve current screen with helper.js injected
+// Serve newest screen with helper.js injected
 app.get('/', (req, res) => {
-  let html = fs.readFileSync(SCREEN_FILE, 'utf-8');
+  const screenFile = getNewestScreen();
+  let html = screenFile ? fs.readFileSync(screenFile, 'utf-8') : WAITING_PAGE;
 
   // Inject helper script before </body>
   const helperScript = fs.readFileSync(path.join(__dirname, 'helper.js'), 'utf-8');
@@ -69,23 +81,35 @@ app.get('/', (req, res) => {
   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' }));
+// Watch for new or changed .html files in the directory
+chokidar.watch(SCREEN_DIR, { ignoreInitial: true })
+  .on('add', (filePath) => {
+    if (filePath.endsWith('.html')) {
+      console.log(JSON.stringify({ type: 'screen-added', file: filePath }));
+      // Notify all browsers to reload
+      clients.forEach(ws => {
+        if (ws.readyState === WebSocket.OPEN) {
+          ws.send(JSON.stringify({ type: 'reload' }));
+        }
+      });
+    }
+  })
+  .on('change', (filePath) => {
+    if (filePath.endsWith('.html')) {
+      console.log(JSON.stringify({ type: 'screen-updated', file: filePath }));
+      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}`,
-    screen_dir: SCREEN_DIR,
-    screen_file: SCREEN_FILE
+    screen_dir: SCREEN_DIR
   }));
 });

+ 1 - 2
lib/brainstorm-server/start-server.sh

@@ -11,7 +11,6 @@ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
 # Generate unique session directory
 SESSION_ID="$$-$(date +%s)"
 SCREEN_DIR="/tmp/brainstorm-${SESSION_ID}"
-SCREEN_FILE="${SCREEN_DIR}/screen.html"
 PID_FILE="${SCREEN_DIR}/.server.pid"
 LOG_FILE="${SCREEN_DIR}/.server.log"
 
@@ -27,7 +26,7 @@ fi
 
 # Start server, capturing output to log file
 cd "$SCRIPT_DIR"
-BRAINSTORM_SCREEN="$SCREEN_FILE" node index.js > "$LOG_FILE" 2>&1 &
+BRAINSTORM_DIR="$SCREEN_DIR" node index.js > "$LOG_FILE" 2>&1 &
 SERVER_PID=$!
 echo "$SERVER_PID" > "$PID_FILE"
 

+ 8 - 7
skills/brainstorming/SKILL.md

@@ -90,10 +90,10 @@ Only proceed if they agree. Otherwise, describe options in text.
 ${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/start-server.sh
 
 # Returns: {"type":"server-started","port":52341,"url":"http://localhost:52341",
-#           "screen_dir":"/tmp/brainstorm-12345","screen_file":"/tmp/brainstorm-12345/screen.html"}
+#           "screen_dir":"/tmp/brainstorm-12345"}
 ```
 
-Save `screen_dir` and `screen_file` from the response. Tell user to open the URL.
+Save `screen_dir` from the response. Tell user to open the URL.
 
 ### The Loop
 
@@ -102,10 +102,11 @@ Save `screen_dir` and `screen_file` from the response. Tell user to open the URL
    ${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/wait-for-feedback.sh $SCREEN_DIR
    ```
 
-2. **Write HTML** to `screen_file`:
-   - First `Read` the screen_file (even if empty) so Write tool works
-   - Then use Write tool - **never use cat/heredoc** (dumps noise into terminal)
-   - If Write fails, read first then retry
+2. **Write HTML** to a new file in `screen_dir`:
+   - Use semantic filenames: `platform.html`, `visual-style.html`, `layout.html`
+   - **Never reuse filenames** - each screen gets a fresh file
+   - Use Write tool - **never use cat/heredoc** (dumps noise into terminal)
+   - Server automatically serves the newest file
 
 3. **Tell user what to expect:**
    - Remind them of the URL (every step, not just first)
@@ -118,7 +119,7 @@ Save `screen_dir` and `screen_file` from the response. Tell user to open the URL
 
 5. **Process feedback** - returns JSON like `{"choice": "a", "feedback": "make header smaller"}`
 
-6. **Iterate or advance** - if feedback changes current screen, update and re-show. Only move to next question when current step is validated.
+6. **Iterate or advance** - if feedback changes current screen, write a new file (e.g., `layout-v2.html`). Only move to next question when current step is validated.
 
 7. Repeat until done.
 

+ 12 - 5
skills/brainstorming/visual-companion.md

@@ -17,18 +17,19 @@ Quick reference for the browser-based visual brainstorming companion.
 ```bash
 # 1. Start server
 ${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/start-server.sh
-# Returns: {"screen_dir":"/tmp/brainstorm-xxx","screen_file":"...","url":"http://localhost:PORT"}
+# Returns: {"screen_dir":"/tmp/brainstorm-xxx","url":"http://localhost:PORT"}
 
 # 2. Start watcher FIRST (background bash) - avoids race condition
 ${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/wait-for-feedback.sh $SCREEN_DIR
 
-# 3. Write HTML to screen_file using Write tool (browser auto-refreshes)
+# 3. Write HTML to a NEW file in screen_dir (e.g., platform.html, style.html)
+#    Never reuse filenames - server serves newest file automatically
 
 # 4. Call TaskOutput(task_id, block=true, timeout=600000)
 #    If timeout, call again. After 3 timeouts (30 min), prompt user.
 # Returns: {"choice":"a","feedback":"user notes"}
 
-# 5. Iterate or advance - update screen if feedback changes it, else next question
+# 5. Iterate or advance - write new file if feedback changes it (e.g., style-v2.html)
 
 # 6. Clean up when done
 ${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/stop-server.sh $SCREEN_DIR
@@ -39,13 +40,19 @@ ${CLAUDE_PLUGIN_ROOT}/lib/brainstorm-server/stop-server.sh $SCREEN_DIR
 - **Always ask first** before starting visual companion
 - **Scale fidelity to the question** - wireframes for layout, polish for polish questions
 - **Explain the question** on each page - what decision are you seeking?
-- **Iterate before advancing** - if feedback changes current screen, update and re-show
+- **Iterate before advancing** - if feedback changes current screen, write new version
 - **2-4 options max** per screen
 
+## File Naming
+
+- **Use semantic names**: `platform.html`, `visual-style.html`, `layout.html`, `controls.html`
+- **Never reuse filenames** - each screen is a new file
+- **For iterations**: append version suffix like `layout-v2.html`, `layout-v3.html`
+- Server automatically serves the newest file by modification time
+
 ## Terminal UX
 
 - **Never use cat/heredoc for HTML** - dumps noise into terminal. Use Write tool instead.
-- **Read screen_file first** before Write (even if empty) to avoid tool errors
 - **Remind user of URL** on every step, not just the first
 - **Give text summary** of what's on screen before they look (e.g., "Showing 3 API structure options")