Plan Canvas
用於計畫與視覺成品的審查循環:你撰寫成品,人類在瀏覽器中審查——精確標註他們所指的元素、聊天、並送出批准計畫 / 要求修改的裁決——而你則在單一個 CLI 呼叫上阻塞等待,其回饋以 JSON 形式回傳。
靈感來自 lavish-axi;以 ECC 原生方式圍繞 /plan 確認閘門重建,零依賴。
使用時機
- 你剛寫好一個計畫成品(
/plan產生的.claude/plans/*.plan.md),需要 CONFIRM/批准決定——畫布裁決取代了手動輸入「yes/繼續」。 - 使用者應該具體指出要修改的內容:審查設計、比較、報告,或任何本機
.md/.html成品。 - 使用者要求
/plan-canvas、視覺審查,或「在瀏覽器中打開」。
請勿用於:差異代碼審查(/code-review)、執行網頁應用程式或遠端 URL。畫布僅提供本機成品檔案。
運作方式
以 ecc-plan-canvas CLI 呼叫——這是 ecc-universal 套件提供的二進位檔(在全域/外掛安裝後位於 PATH 中;外掛安裝亦可使用 node "$CLAUDE_PLUGIN_ROOT/scripts/plan-canvas.js")。從你正在審查的專案目錄中執行;它可從任何工作目錄運作。它管理一個所有工作階段共用的 detached loopback 伺服器(127.0.0.1:4517),以成品路徑為鍵——無需追蹤工作階段 ID。
此工作流程是純 CLI 加 JSON 的迴圈,因此與模型和 harness 無關:任何能執行 shell 指令並讀取 stdout 的代理皆可相同驅動(Claude Code、Codex、Cursor、Gemini、OpenCode、Copilot)。依你的 harness 如何顯示技能來觸發——例如 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 實際 parked 在工作階段上時,回饋才會送達你。如果你的回合結束時沒有任何東西在監聽,訊息會留在佇列中,而從人類的視角來看,送出訊息似乎完全沒反應。
因此,請在你的 harness 支援時,將 await 作為背景任務執行(在 Claude Code 中,使用 run_in_background: true 的 Bash 呼叫)。它會在回饋送達時立即退出,並由 harness 將 JSON 交給你,這樣迴圈就能跨回合持續存活,而不會隨前景呼叫終止。前景 await 也可用,但僅限於 harness 設定時間限制之前。
存在兩個備援機制,但都不能作為省略上述做法的藉口:
ecc-plan-canvas pending列出已排入佇列但沒有監聽器的回饋。每當你不確定是否錯過訊息時,可檢查此項。stop:plan-canvas-pending鉤子會在你的回合中有未送達的畫布回饋時阻止回合結束,並將訊息交給你。如果你從該鉤子讀取回饋,代表你太早停止監聽了。
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 模型或依賴關係圖時,請將其撰寫為被 fence 包圍的 ```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)必須放在成品旁,並以相對路徑引用。
- 伺服器僅限 loopback,並在 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。