plan-canvas

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Plan Canvas

计划画布(Plan Canvas)

Review loop for plans and visual artifacts: you write the artifact, the human reviews it in the browser — annotating the exact element they mean, chatting, and delivering an Approve plan / Request changes verdict — while you block on a single CLI call that returns their feedback as JSON.
Inspired by lavish-axi; rebuilt ECC-native around the
/plan
confirmation gate, with zero dependencies.
计划与可视化工件的审核循环:你编写工件,用户在浏览器中进行审核——标注他们所指的具体元素、聊天,并给出批准计划/请求修改的结论——而你只需通过一个CLI调用即可等待,该调用会以JSON格式返回他们的反馈。
灵感来自lavish-axi;围绕
/plan
确认环节重构为ECC原生工具,无任何依赖。

When to Use

使用场景

  • You just wrote a plan artifact (
    .claude/plans/*.plan.md
    from
    /plan
    ) and need the CONFIRM/approve decision — the canvas verdict replaces a typed "yes/proceed".
  • The user should point at what to change: reviewing designs, comparisons, reports, or any local
    .md
    /
    .html
    artifact.
  • The user asks for
    /plan-canvas
    , a visual review, or "open it in the browser".
Do NOT use for: code review of diffs (
/code-review
), running web apps, or remote URLs. The canvas serves local artifact files only.
  • 你刚编写完一个计划工件(来自
    /plan
    .claude/plans/*.plan.md
    文件),需要获取CONFIRM/批准决定——画布的结论可替代输入“yes/继续”。
  • 用户需要指向要修改的内容:审核设计方案、对比内容、报告或任何本地
    .md
    /
    .html
    工件。
  • 用户要求使用
    /plan-canvas
    、进行可视化审核,或者“在浏览器中打开它”。
请勿用于:代码差异审核(使用
/code-review
)、运行Web应用或访问远程URL。画布仅支持本地工件文件。

How It Works

工作原理

Invoke the CLI as
ecc-plan-canvas
— the bin shipped by the
ecc-universal
package (on PATH after a global/plugin install;
node "$CLAUDE_PLUGIN_ROOT/scripts/plan-canvas.js"
also works for plugin installs). Run it from the project you are reviewing in; it works from any working directory. It manages a detached loopback server (
127.0.0.1:4517
) shared by all sessions, keyed by artifact path — no session ids to track.
The workflow is a plain CLI-plus-JSON loop, so it is model- and harness-agnostic: any agent that can run a shell command and read stdout drives it the same way (Claude Code, Codex, Cursor, Gemini, OpenCode, Copilot). Trigger it however your harness surfaces skills — e.g.
/plan-canvas
in Claude Code,
$plan-canvas
in Codex — or just run the
ecc-plan-canvas
commands directly.
bash
undefined
调用CLI命令
ecc-plan-canvas
——该命令由
ecc-universal
包提供(全局/插件安装后会加入PATH;插件安装时也可使用
node "$CLAUDE_PLUGIN_ROOT/scripts/plan-canvas.js"
)。在你要审核的项目目录中运行它;它可在任何工作目录下运行。它会管理一个独立的环回服务器(
127.0.0.1:4517
),供所有会话共享,按工件路径进行标识——无需跟踪会话ID。
该工作流是一个简单的CLI加JSON循环,因此它与模型和 harness 无关:任何能运行shell命令并读取标准输出的Agent都可以以相同方式驱动它(Claude Code、Codex、Cursor、Gemini、OpenCode、Copilot)。可通过你的harness提供的技能触发它——例如在Claude Code中使用
/plan-canvas
,在Codex中使用
$plan-canvas
——或者直接运行
ecc-plan-canvas
命令。
bash
undefined

1. Open the artifact in the user's browser (returns immediately)

1. 在用户浏览器中打开工件(立即返回)

ecc-plan-canvas open .claude/plans/feature.plan.md
ecc-plan-canvas open .claude/plans/feature.plan.md

2. Block until the human responds. Leave running; re-run if interrupted:

2. 等待用户响应。保持运行;若中断可重新运行:

queued feedback is never lost.

已排队的反馈不会丢失。

ecc-plan-canvas await .claude/plans/feature.plan.md
undefined
ecc-plan-canvas await .claude/plans/feature.plan.md
undefined

Stay listening, or the human talks to an empty chair

持续监听,否则用户将对着空椅子交流

Feedback only reaches you while an
await
is actually parked on the session. If your turn ends with nothing listening, the message sits in the queue and, from the human's side of the glass, sending appears to do nothing at all.
So run
await
as a background task
when your harness supports one (in Claude Code, a Bash call with
run_in_background: true
). It exits the moment feedback arrives and the harness hands you the JSON, which keeps the loop alive across turns instead of dying with the foreground call. A foreground
await
works too, but only until the harness time-limits it.
Two backstops exist, and neither is an excuse to skip the above:
  • ecc-plan-canvas pending
    lists feedback queued with no listener. Check it whenever you are unsure whether you missed something.
  • The
    stop:plan-canvas-pending
    hook blocks your turn from ending while canvas feedback is undelivered, and hands you the messages. If you are reading feedback from that hook, you stopped listening too early.
await
prints JSON when the human acts:
json
{
  "status": "feedback",
  "items": [
    { "kind": "annotation", "text": "Split this into two phases",
      "anchor": { "selector": "h2:nth-of-type(3)", "tag": "h2", "snippet": "Phase 2: Migration" } },
    { "kind": "verdict", "verdict": "request-changes" }
  ]
}
  • kind: "chat"
    — freeform message; answer in the canvas, not the terminal.
  • kind: "annotation"
    — feedback anchored to an element (
    anchor.selector
    ,
    anchor.snippet
    show what they pointed at;
    anchor.textRange.text
    when they highlighted a passage).
  • kind: "verdict"
    approve
    means the plan is CONFIRMED: stop polling, end the session, and start implementing.
    request-changes
    means revise the artifact (the canvas live-reloads it) and keep the loop going.
3. Always respond in the canvas, then keep listening. One command does both:
bash
ecc-plan-canvas await <file> --reply "Split Phase 2 as requested. Take a look."
Every human message gets a reply in the canvas, even a one-liner like "On it, rewriting the risk table now." Silence in the chat panel is indistinguishable from a broken canvas, which is exactly the failure this loop exists to prevent. Answer there, not only in the terminal.
While you work, keep the chat honest with the activity indicator:
bash
undefined
只有当
await
实际处于会话等待状态时,反馈才能传递给你。如果你的回合结束时没有任何监听进程,消息会留在队列中,从用户的角度看,发送操作似乎没有任何反应。
因此,当你的harness支持后台任务时,将
await
作为后台任务运行
(在Claude Code中,使用带有
run_in_background: true
的Bash调用)。一旦反馈到达且harness将JSON传递给你,它就会退出,这样可以保持循环在多个回合中持续,而不会随前台调用结束而终止。前台
await
也可以工作,但仅在harness的时间限制内有效。
有两个后备机制,但这不能成为跳过上述操作的借口:
  • ecc-plan-canvas pending
    列出所有未被监听的已排队反馈。当你不确定是否遗漏了内容时,可使用该命令查看。
  • stop:plan-canvas-pending
    钩子会在画布反馈未交付时阻止你的回合结束,并将消息传递给你。如果你从该钩子读取反馈,说明你停止监听的时间过早。
当用户操作时,
await
会输出JSON:
json
{
  "status": "feedback",
  "items": [
    { "kind": "annotation", "text": "Split this into two phases",
      "anchor": { "selector": "h2:nth-of-type(3)", "tag": "h2", "snippet": "Phase 2: Migration" } },
    { "kind": "verdict", "verdict": "request-changes" }
  ]
}
  • kind: "chat"
    ——自由格式消息;在画布中回复,而非终端。
  • kind: "annotation"
    ——锚定到元素的反馈(
    anchor.selector
    anchor.snippet
    显示用户指向的内容;当用户高亮一段文本时,
    anchor.textRange.text
    会显示该文本)。
  • kind: "verdict"
    ——
    approve
    表示计划已确认:停止轮询,结束会话,开始实施。
    request-changes
    表示需要修订工件(画布会自动重新加载)并继续循环。
3. 始终在画布中回复,然后继续监听。一个命令即可完成这两项操作:
bash
ecc-plan-canvas await <file> --reply "Split Phase 2 as requested. Take a look."
每条用户消息都要在画布中回复,即使是像“正在处理,现在重写风险表。”这样的简短回复。聊天面板中的沉默与画布故障无法区分,而这正是该循环要避免的失败情况。在画布中回复,而不仅仅是在终端中。
在你工作时,使用活动指示器保持聊天状态的真实性:
bash
undefined

animated "agent is thinking..." bubble; refresh it during long work

动画“agent正在思考...”气泡;长时间工作时刷新状态

ecc-plan-canvas typing <file> --state thinking
ecc-plan-canvas typing <file> --state thinking

switch to "agent is typing..." just before a reply lands

在回复即将发出前切换为“agent正在输入...”

ecc-plan-canvas typing <file> --state typing

`await` sets `thinking` for you the moment it hands you a batch, and `--reply`
clears it. Both states self-expire, so a crashed agent decays to an honest
"queued" instead of leaving the human watching dots forever. Refresh `thinking`
if a revision takes more than a minute.

**4. End** when review concludes: `ecc-plan-canvas end <file>`.
ecc-plan-canvas typing <file> --state typing

`await`在将一批反馈传递给你的瞬间会自动设置`thinking`状态,而`--reply`会清除该状态。这两种状态都会自动过期,因此如果Agent崩溃,状态会恢复为诚实的“已排队”,而不会让用户一直看着加载点。如果修订需要超过一分钟,请刷新`thinking`状态。

**4. 结束**:当审核完成时,运行`ecc-plan-canvas end <file>`。

Diagrams (Mermaid)

图表(Mermaid)

When part of the plan is a flow, architecture, sequence, state machine, ER model, or dependency graph, author it as a fenced
```mermaid
block instead of ASCII art or a wall of prose — the canvas renders it as a themed diagram the human can point at. Reach for it when a picture reads faster than a paragraph; skip it for simple lists or tables.
markdown
```mermaid
flowchart LR
  A[Market resolves] --> B{Watchers?}
  B -->|yes| C[Enqueue jobs] --> D[Fan-out worker]
```
Diagrams render in the ECC dark theme with the accent palette. Mermaid loads in the browser from a pinned CDN; if that is unavailable (offline), the block degrades to showing its source, so the review is never blocked. Point a local mirror at
ECC_PLAN_CANVAS_MERMAID_URL
for air-gapped use.
当计划的一部分是流程图、架构图、序列图、状态机、ER模型或依赖图时,将其编写为带围栏的
```mermaid
块,而非ASCII艺术或大段文字——画布会将其渲染为主题化图表,用户可以指向图表进行标注。当图片比段落更易读时使用该方式;对于简单列表或表格则无需使用。
markdown
```mermaid
flowchart LR
  A[Market resolves] --> B{Watchers?}
  B -->|yes| C[Enqueue jobs] --> D[Fan-out worker]
```
图表会以ECC深色主题和强调色板渲染。Mermaid从固定的CDN加载到浏览器中;如果CDN不可用(离线状态),该块会降级显示其源代码,因此审核不会被阻塞。对于离线使用场景,可将本地镜像指向
ECC_PLAN_CANVAS_MERMAID_URL

Rules

规则

  • Markdown artifacts render in ECC's plan template (including Mermaid blocks);
    .html
    artifacts render as-is with the annotation layer injected. For HTML authoring guidance use the
    frontend-design-direction
    and
    artifact-design
    skills.
  • Edit the artifact file to revise — the canvas live-reloads on save. Never re-run
    open
    to refresh.
  • {"status": "ended", "endedBy": "user"}
    (or
    sessionEnded: true
    on a feedback batch) means the user closed the review: stop polling, deliver remaining updates in chat, and do not reopen. A plain
    open
    on that session is refused; pass
    --reopen
    only when the user asks to resume.
  • Sibling assets (images, CSS) must sit next to the artifact and be referenced by relative path.
  • The server is loopback-only and exits after 30 idle minutes (
    ECC_PLAN_CANVAS_IDLE_MS
    );
    stop
    shuts it down explicitly. State lives in
    ~/.claude/plan-canvas/
    (
    ECC_PLAN_CANVAS_STATE_DIR
    ).
  • 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
    配置)。

Examples

示例

Plan approval flow
/plan
writes
.claude/plans/notifications.plan.md
and must WAIT for confirmation:
bash
ecc-plan-canvas open .claude/plans/notifications.plan.md
ecc-plan-canvas await .claude/plans/notifications.plan.md
计划批准流程——
/plan
生成
.claude/plans/notifications.plan.md
,必须等待确认:
bash
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"}]}

→ {"status":"feedback","items":[{"kind":"verdict","verdict":"approve"}]}

ecc-plan-canvas end .claude/plans/notifications.plan.md
ecc-plan-canvas end .claude/plans/notifications.plan.md

plan is confirmed — begin implementation

计划已确认——开始实施


**Revision loop** — feedback arrives, you edit the file, reply, keep listening:

```bash

**修订循环**——收到反馈后,编辑文件,回复,继续监听:

```bash

await returned annotations → edit the .plan.md (canvas live-reloads)

await返回标注信息 → 编辑.plan.md文件(画布自动重新加载)

ecc-plan-canvas await <file> --reply "Reworked the risk table."
ecc-plan-canvas await <file> --reply "已重新整理风险表。"

→ blocks again until the next response

→ 再次阻塞,等待下一次响应

undefined
undefined

Anti-Patterns

反模式

  • Polling with
    --timeout-ms
    in a loop. It exists for tests. Leave the plain
    await
    running instead.
  • Ending your turn with no
    await
    listening while the review is still open. That is the one failure the human experiences as "I sent a message and nothing happened".
  • Reading the feedback but answering only in the terminal. The human is looking at the canvas.
  • Reopening after a user-initiated end "just to show" something.
  • Pasting the whole plan into chat and opening a canvas — pick the canvas and keep the terminal summary to one line.
  • Parsing the canvas chat from state files — everything you need arrives via
    await
    .
  • 在循环中使用
    --timeout-ms
    进行轮询。该参数仅用于测试。应保持普通
    await
    持续运行。
  • 当审核仍在进行时,结束回合且没有
    await
    监听。这是用户会遇到的唯一一种“我发送了消息但没有任何反应”的故障情况。
  • 读取反馈但仅在终端中回复。用户正在查看画布。
  • 用户主动结束审核后,“只是为了展示”而重新打开。
  • 将整个计划粘贴到聊天中同时打开画布——选择画布,并将终端摘要控制为一行。
  • 从状态文件解析画布聊天内容——你需要的所有信息都会通过
    await
    传递。