Plan Canvas (計劃畫布)
計劃與視覺成品的審查循環:你撰寫成品,人類在瀏覽器中進行審查——精確標註他們所指的元素、進行即時聊天,並送出 批准計劃 / 要求變更 的裁決——而你則在一個 CLI 調用上阻塞等待,該調用會以 JSON 格式回傳他們的反饋。
靈感來自 lavish-axi;在 ECC 原生環境中圍繞 /plan 確認閘門重建,零依賴。
何時使用
- 你剛寫好一個計劃成品(來自
/plan的.claude/plans/*.plan.md),並且需要 CONFIRM/批准決策——畫布裁決取代了手動輸入的「yes/proceed」。 - 使用者應該 指出 要修改的內容:審查設計稿、比較表、報告,或任何本機的
.md/.html成品。 - 使用者要求
/plan-canvas、視覺審查,或「在瀏覽器中開啟」。
不要用於: 差異代碼審查 (/code-review)、執行中的網頁應用程式,或遠端 URL。畫布僅服務於本機成品檔案。
運作方式
將 CLI 調用為 ecc-plan-canvas——這是 ecc-universal 套件提供的二進位檔(在全域/外掛安裝後可在 PATH 中找到;外掛安裝也可使用 node "$CLAUDE_PLUGIN_ROOT/scripts/plan-canvas.js")。從你正在審查的專案目錄中執行;它可以在任何工作目錄下運作。它管理一個共用的分離迴路伺服器 (127.0.0.1:4517),由所有工作階段共用,並以成品路徑作為鍵值——無需追蹤工作階段 ID。
工作流程是一個純 CLI 加 JSON 的迴圈,因此它與模型和框架無關:任何能夠執行 shell 命令並讀取標準輸出的代理都能以相同方式驅動它(Claude Code、Codex、Cursor、Gemini、OpenCode、Copilot)。根據你的框架如何顯示技能來觸發它——例如在 Claude Code 中使用 /plan-canvas,在 Codex 中使用 $plan-canvas——或者直接執行 ecc-plan-canvas 命令。
# 1. 在使用者的瀏覽器中開啟成品(立即返回)
ecc-plan-canvas open .claude/plans/feature.plan.md
# 2. 阻塞等待直到人類回應。保持執行;如果中斷則重新執行:
# 佇列中的反饋絕不會遺失。
ecc-plan-canvas await .claude/plans/feature.plan.md
保持監聽,否則人類會對著空椅子說話
反饋只有在 await 實際掛在工作階段上時才會送達。如果你的回合結束時沒有東西在監聽,訊息就會停留在佇列中,而從人類那一端看來,發送訊息似乎完全沒有作用。
因此,當你的框架支援後台任務時(在 Claude Code 中,是帶有 run_in_background: true 的 Bash 調用),請將 await 作為後台任務執行。它會在反饋到達時立即退出,框架會將 JSON 交給你,這樣迴圈就能跨回合保持活躍,而不是隨著前景調用而終止。前景 await 也能運作,但僅限於框架設定的時間限制之內。
存在兩個後備機制,但這都不是跳過上述做法的藉口:
ecc-plan-canvas pending會列出沒有監聽者的佇列反饋。在不確定是否錯過訊息時檢查它。stop:plan-canvas-pending鉤子會在 Canvas 反饋尚未送達時阻止你的回合結束,並將訊息交給你。如果你從該鉤子中讀取反饋,表示你過早停止監聽了。
當人類採取行動時,await 會印出 JSON:
{
"status": "feedback",
"items": [
{ "kind": "annotation", "text": "將此拆分為兩個階段",
"anchor": { "selector": "h2:nth-of-type(3)", "tag": "h2", "snippet": "階段 2:遷移" } },
{ "kind": "verdict", "verdict": "request-changes" }
]
}
kind: "chat"— 自由格式訊息;在畫布中回覆,而非終端機。kind: "annotation"— 錨定到某個元素的反饋(anchor.selector、anchor.snippet顯示他們指向的內容;anchor.textRange.text顯示他們反白選取的段落)。kind: "verdict"—approve表示計劃已確認:停止輪詢、結束工作階段,並開始實作。request-changes表示修改成品(畫布會即時重新載入)並繼續迴圈。
3. 始終在畫布中回應,然後繼續監聽。一個命令就能同時完成兩件事:
ecc-plan-canvas await <file> --reply "已按要求拆分階段 2。請過目。"
每一條人類訊息都應在畫布中得到回覆,即使是像「收到,正在重寫風險表」這樣的一句話。聊天面板中的沉默與畫布故障無異,而這正是此迴圈旨在防止的失敗。在那裡回覆,而不僅僅在終端機中。
在你工作期間,使用活動指示器讓聊天保持真實:
# 動畫「代理正在思考...」氣泡;在長時間工作期間刷新它
ecc-plan-canvas typing <file> --state thinking
# 在回覆送出前切換為「代理正在輸入...」
ecc-plan-canvas typing <file> --state typing
await 在將一批訊息交給你時會自動設定 thinking,而 --reply 會清除它。兩種狀態都會自動過期,因此崩潰的代理會誠實地顯示為「已佇列」,而不是讓人類看著永遠的點點點。如果修改需要超過一分鐘,請刷新 thinking。
4. 結束 當審查結束時:ecc-plan-canvas end <file>。
圖表(Mermaid)
當計劃的一部分是流程、架構、序列、狀態機、ER 模型或依賴關係圖時,請將其撰寫為圍欄 ```mermaid 程式碼區塊,而不是 ASCII 藝術或大量文字——畫布會將其渲染為帶主題的圖表,人類可以指向它。當圖片的閱讀速度快於段落時使用;對於簡單的列表或表格則跳過。
```mermaid
flowchart LR
A[市場結算] --> B{有觀察者?}
B -->|是| C[將任務加入佇列] --> D[扇形擴散工人]
```
圖表會以 ECC 深色主題和強調色調色板渲染。Mermaid 從固定的 CDN 在瀏覽器中載入;如果無法使用(離線),區塊會退而顯示其原始碼,因此審查永遠不會受阻。對於氣隙環境,請將本機鏡像指向 ECC_PLAN_CANVAS_MERMAID_URL。
規則
- Markdown 成品會以 ECC 的計劃範本渲染(包括 Mermaid 區塊);
.html成品則會原樣渲染並注入標註層。關於 HTML 編寫指導,請使用frontend-design-direction和artifact-design技能。 - 編輯成品檔案以進行修改——畫布會在儲存時即時重新載入。永遠不要重新執行
open來重新整理。 {"status": "ended", "endedBy": "user"}(或回饋批次中的sessionEnded: true)表示使用者已關閉審查:停止輪詢,在聊天中傳達剩餘更新,並且不要重新開啟。對該工作階段的普通open將被拒絕;僅在使用者要求繼續時傳遞--reopen。- 同層級資源(圖片、CSS)必須放在成品旁邊,並使用相對路徑引用。
- 伺服器僅限迴路,並在空閒 30 分鐘後退出(
ECC_PLAN_CANVAS_IDLE_MS);stop會明確關閉它。狀態儲存在~/.claude/plan-canvas/中(ECC_PLAN_CANVAS_STATE_DIR)。
範例
計劃批准流程 — /plan 寫入 .claude/plans/notifications.plan.md 並且必須等待確認:
ecc-plan-canvas open .claude/plans/notifications.plan.md
ecc-plan-canvas await .claude/plans/notifications.plan.md
# → {"status":"feedback","items":[{"kind":"verdict","verdict":"approve"}]}
ecc-plan-canvas end .claude/plans/notifications.plan.md
# 計劃已確認 — 開始實作
修改迴圈 — 收到反饋,你編輯檔案,回覆,繼續監聽:
# await 返回了標註 → 編輯 .plan.md(畫布即時重新載入)
ecc-plan-canvas await <file> --reply "已重寫風險表。"
# → 再次阻塞直到得到下一個回應
反模式
- 在迴圈中使用
--timeout-ms進行輪詢。它只存在於測試中。讓普通的await保持執行。 - 在審查仍在進行時結束你的回合,而且沒有
await在監聽。這是人類體驗為「我發送了訊息但什麼也沒發生」的唯一一種失敗。 - 讀取反饋但只在終端機中回覆。人類正在看著畫布。
- 在使用者發起結束後重新開啟,「只是為了展示」某些東西。
- 將整個計劃貼到聊天中 並且 開啟一個畫布——選擇畫布,並將終端機摘要限制在一行。
- 從狀態檔案解析畫布聊天——你需要的一切都會透過
await送達。
設計筆記與起源:docs/design/plan-canvas.md。