Skip to content

可视化头脑风暴重构:浏览器显示与终端命令

由 Markdown 原样翻译并转换为 Astro Starlight MDX 格式。

日期: 2026-02-19 状态: 已批准 范围: lib/brainstorm-server/skills/brainstorming/visual-companion.mdtests/brainstorm-server/

在视觉头脑风暴期间,Claude 将 wait-for-feedback.sh 作为后台任务运行,并阻塞在 TaskOutput(block=true, timeout=600s) 上。这会完全占用 TUI —— 当视觉头脑风暴正在运行时,用户无法向 Claude 输入内容。浏览器变成了唯一的输入通道。

Claude Code 的执行模型是基于轮次的。Claude 无法在单个轮次内同时监听两个通道。阻塞式 TaskOutput 模式是错误的原语 —— 它模拟了平台并不支持的事件驱动行为。

浏览器 = 交互式显示。 显示模型图,让用户点击选择选项。选择会记录在服务器端。

终端 = 对话通道。 始终不被阻塞,始终可用。用户在这里与 Claude 交谈。

  1. Claude 将一个 HTML 文件写入会话目录
  2. 服务器通过 chokidar 检测到它,向浏览器推送 WebSocket reload(不变)
  3. Claude 结束它的轮次 —— 告诉用户查看浏览器并在终端中回复
  4. 用户查看浏览器,可选择点击一个选项,然后在终端中输入反馈
  5. 在下一轮中,Claude 读取 $SCREEN_DIR/.events 以获取浏览器交互流(点击、选择),并与终端文本合并
  6. 迭代或推进

没有后台任务。没有 TaskOutput 阻塞。没有轮询脚本。

完全删除。它的目的是桥接 “服务器将事件记录到 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-startedscreen-added)。这使其保持小而聚焦。
  • Claude 可以读取完整流来理解用户的探索模式,也可以只查看最后一个 choice 事件作为最终选择。

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)。更简单,并且不会因模板格式变化而损坏。

移除:

  • feedback-footer div(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

移除:

  • 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 —— 已不再存在

重写 “The Loop” 章节 为上面描述的非阻塞流程。移除所有对以下内容的引用:

  • wait-for-feedback.sh
  • TaskOutput 阻塞
  • timeout/retry 逻辑(600 秒超时、30 分钟上限)
  • 描述 send-to-claude JSON 的 “User Feedback Format” 章节

替换为:

  • 新循环(写 HTML → 结束轮次 → 用户在终端回复 → 读取 .events → 迭代)
  • .events 文件格式文档
  • 指导说明:终端消息是主要反馈;.events 提供完整浏览器交互流作为额外上下文

保留:

  • 服务器启动/关闭说明
  • 内容片段与完整文档指导
  • CSS 类参考和可用组件
  • 设计提示(根据问题调整保真度、每屏 2-4 个选项等)

完全删除。

需要更新的测试:

  • 断言片段响应中存在 feedback-footer 的测试 —— 更新为断言选择指示条或 <!-- CONTENT --> 替换
  • 断言 helper.js 包含 send 的测试 —— 更新以反映收窄后的 API
  • 断言 sendToClaude CSS 变量使用的测试 —— 移除(该函数不再存在)

服务器代码(index.jshelper.jsframe-template.html)完全平台无关 —— 纯 Node.js 和浏览器 JavaScript。没有 Claude Code 特定引用。已经证明可以通过后台终端交互在 Codex 上运行。

技能说明(visual-companion.md)是平台自适应层。每个平台的 Claude 使用自己的工具来启动服务器、读取 .events 等。非阻塞模型可以自然地跨平台工作,因为它不依赖任何平台特定的阻塞原语。

  • 在视觉头脑风暴期间 TUI 始终响应
  • 混合输入 —— 在浏览器中点击 + 在终端中输入,自然合并
  • 优雅降级 —— 浏览器宕机或用户没有打开它?终端仍然可用
  • 更简单的架构 —— 没有后台任务、没有轮询脚本、没有超时管理
  • 跨平台 —— 相同服务器代码可在 Claude Code、Codex 和任何未来平台上运行
  • 纯浏览器反馈工作流 —— 用户必须返回终端继续。选择指示条会引导他们,但相比旧的点击 Send 并等待流程,多了一个额外步骤。
  • 来自浏览器的内联文本反馈 —— textarea 被移除。所有文本反馈都通过终端进行。这是有意为之 —— 终端是比框架中的小 textarea 更好的文本输入通道。
  • 点击浏览器 Send 后立即响应 —— 旧系统会在用户点击 Send 的那一刻让 Claude 响应。现在用户切换到终端时会有间隙。实践中这只是几秒钟,并且用户可以在终端消息中添加上下文。
-
0:000:00