日期: 2026-02-19
状态: 已批准
范围: lib/brainstorm-server/、skills/brainstorming/visual-companion.md、tests/brainstorm-server/
在视觉头脑风暴期间,Claude 将 wait-for-feedback.sh 作为后台任务运行,并阻塞在 TaskOutput(block=true, timeout=600s) 上。这会完全占用 TUI —— 当视觉头脑风暴正在运行时,用户无法向 Claude 输入内容。浏览器变成了唯一的输入通道。
Claude Code 的执行模型是基于轮次的。Claude 无法在单个轮次内同时监听两个通道。阻塞式 TaskOutput 模式是错误的原语 —— 它模拟了平台并不支持的事件驱动行为。
浏览器 = 交互式显示。 显示模型图,让用户点击选择选项。选择会记录在服务器端。
终端 = 对话通道。 始终不被阻塞,始终可用。用户在这里与 Claude 交谈。
- Claude 将一个 HTML 文件写入会话目录
- 服务器通过 chokidar 检测到它,向浏览器推送 WebSocket reload(不变)
- Claude 结束它的轮次 —— 告诉用户查看浏览器并在终端中回复
- 用户查看浏览器,可选择点击一个选项,然后在终端中输入反馈
- 在下一轮中,Claude 读取
$SCREEN_DIR/.events以获取浏览器交互流(点击、选择),并与终端文本合并 - 迭代或推进
没有后台任务。没有 TaskOutput 阻塞。没有轮询脚本。
关键删除:wait-for-feedback.sh
Section titled “关键删除:wait-for-feedback.sh”完全删除。它的目的是桥接 “服务器将事件记录到 stdout” 和 “Claude 需要接收这些事件”。.events 文件取代了这一点 —— 服务器直接写入用户交互事件,而 Claude 使用平台提供的任何文件读取机制来读取它们。
关键新增:.events 文件(每个屏幕的事件流)
Section titled “关键新增:.events 文件(每个屏幕的事件流)”服务器将所有用户交互事件写入 $SCREEN_DIR/.events,每行一个 JSON 对象。这为 Claude 提供当前屏幕的完整交互流 —— 不仅是最终选择,还包括用户的探索路径(点击 A,然后 B,最终停在 C)。
用户探索选项后的示例内容:
{"type":"click","choice":"a","text":"Option A - Preset-First Wizard","timestamp":1706000101}{"type":"click","choice":"c","text":"Option C - Manual Config","timestamp":1706000108}{"type":"click","choice":"b","text":"Option B - Hybrid Approach","timestamp":1706000115}- 在一个屏幕内追加写入。每个用户事件都会作为新行追加。
- 当 chokidar 检测到新的 HTML 文件(推送新屏幕)时,该文件会被清空(删除),防止陈旧事件延续到下一屏。
- 如果 Claude 读取时该文件不存在,则表示没有发生浏览器交互 —— Claude 只使用终端文本。
- 该文件只包含用户事件(
click等)—— 不包含服务器生命周期事件(server-started、screen-added)。这使其保持小而聚焦。 - Claude 可以读取完整流来理解用户的探索模式,也可以只查看最后一个
choice事件作为最终选择。
按文件划分的变更
Section titled “按文件划分的变更”index.js(服务器)
Section titled “index.js(服务器)”A. 将用户事件写入 .events 文件。
在 WebSocket message 处理器中,在将事件记录到 stdout 后:通过 fs.appendFileSync 将事件作为 JSON 行追加到 $SCREEN_DIR/.events。只写入用户交互事件(带有 source: 'user-event' 的事件),不写入服务器生命周期事件。
B. 在新屏幕上清空 .events。
在 chokidar 的 add 处理器中(检测到新的 .html 文件),如果 $SCREEN_DIR/.events 存在则删除它。这是确定的“新屏幕”信号 —— 比在 GET / 时清空更好,因为后者会在每次 reload 时触发。
C. 替换 wrapInFrame 内容注入。
当前正则锚定在 <div class="feedback-footer"> 上,而该元素将被移除。替换为注释占位符:移除 #claude-content 内现有默认内容(<h2>Visual Brainstorming</h2> 和副标题段落),并替换为一个 <!-- CONTENT --> 标记。内容注入变为 frameTemplate.replace('<!-- CONTENT -->', content)。更简单,并且不会因模板格式变化而损坏。
frame-template.html(UI 框架)
Section titled “frame-template.html(UI 框架)”移除:
feedback-footerdiv(textarea、Send 按钮、label、.feedback-row)- 关联 CSS(
.feedback-footer、.feedback-footer label、.feedback-row、其中的 textarea 和 button 样式)
添加:
#claude-content内的<!-- CONTENT -->占位符,用来替换默认文本- 原 footer 位置的选择指示条,具有两种状态:
- 默认:“Click an option above, then return to the terminal”
- 选择后:“Option B selected — return to terminal to continue”
- 指示条 CSS(微妙,视觉权重与现有 header 类似)
保持不变:
- 带有 “Brainstorm Companion” 标题和连接状态的 header 栏
.main包装器和#claude-content容器- 所有组件 CSS(
.options、.cards、.mockup、.split、.pros-cons、placeholders、mock elements) - 深色/浅色主题变量和 media query
helper.js(客户端脚本)
Section titled “helper.js(客户端脚本)”移除:
sendToClaude()函数和 “Sent to Claude” 页面接管window.send()函数(它绑定到已移除的 Send 按钮)- 表单提交处理器 —— 没有 feedback textarea 后没有用途,还会增加日志噪声
- 输入变化处理器 —— 同样原因
pageshow事件监听器(它原本用于修复 textarea 持久化 —— 现在没有 textarea 了)
保留:
- WebSocket 连接、重连逻辑、事件队列
- Reload 处理器(服务器推送时执行
window.location.reload()) - 用于选择高亮的
window.toggleSelect() window.selectedChoice跟踪window.brainstorm.send()和window.brainstorm.choice()—— 它们不同于被移除的window.send()。它们调用sendEvent,通过 WebSocket 将日志发送到服务器。对自定义完整文档页面有用。
收窄:
- 点击处理器:只捕获
[data-choice]点击,而不是所有按钮/链接。以前浏览器是反馈通道时需要宽泛捕获;现在它只用于选择跟踪。
添加:
- 在
data-choice点击时,更新选择指示条文本以显示选中的选项。
从 window.brainstorm API 中移除:
brainstorm.sendToClaude—— 已不再存在
visual-companion.md(技能说明)
Section titled “visual-companion.md(技能说明)”重写 “The Loop” 章节 为上面描述的非阻塞流程。移除所有对以下内容的引用:
wait-for-feedback.shTaskOutput阻塞- timeout/retry 逻辑(600 秒超时、30 分钟上限)
- 描述
send-to-claudeJSON 的 “User Feedback Format” 章节
替换为:
- 新循环(写 HTML → 结束轮次 → 用户在终端回复 → 读取
.events→ 迭代) .events文件格式文档- 指导说明:终端消息是主要反馈;
.events提供完整浏览器交互流作为额外上下文
保留:
- 服务器启动/关闭说明
- 内容片段与完整文档指导
- CSS 类参考和可用组件
- 设计提示(根据问题调整保真度、每屏 2-4 个选项等)
wait-for-feedback.sh
Section titled “wait-for-feedback.sh”完全删除。
tests/brainstorm-server/server.test.js
Section titled “tests/brainstorm-server/server.test.js”需要更新的测试:
- 断言片段响应中存在
feedback-footer的测试 —— 更新为断言选择指示条或<!-- CONTENT -->替换 - 断言
helper.js包含send的测试 —— 更新以反映收窄后的 API - 断言
sendToClaudeCSS 变量使用的测试 —— 移除(该函数不再存在)
服务器代码(index.js、helper.js、frame-template.html)完全平台无关 —— 纯 Node.js 和浏览器 JavaScript。没有 Claude Code 特定引用。已经证明可以通过后台终端交互在 Codex 上运行。
技能说明(visual-companion.md)是平台自适应层。每个平台的 Claude 使用自己的工具来启动服务器、读取 .events 等。非阻塞模型可以自然地跨平台工作,因为它不依赖任何平台特定的阻塞原语。
这会带来什么
Section titled “这会带来什么”- 在视觉头脑风暴期间 TUI 始终响应
- 混合输入 —— 在浏览器中点击 + 在终端中输入,自然合并
- 优雅降级 —— 浏览器宕机或用户没有打开它?终端仍然可用
- 更简单的架构 —— 没有后台任务、没有轮询脚本、没有超时管理
- 跨平台 —— 相同服务器代码可在 Claude Code、Codex 和任何未来平台上运行
这会放弃什么
Section titled “这会放弃什么”- 纯浏览器反馈工作流 —— 用户必须返回终端继续。选择指示条会引导他们,但相比旧的点击 Send 并等待流程,多了一个额外步骤。
- 来自浏览器的内联文本反馈 —— textarea 被移除。所有文本反馈都通过终端进行。这是有意为之 —— 终端是比框架中的小 textarea 更好的文本输入通道。
- 点击浏览器 Send 后立即响应 —— 旧系统会在用户点击 Send 的那一刻让 Claude 响应。现在用户切换到终端时会有间隙。实践中这只是几秒钟,并且用户可以在终端消息中添加上下文。