From bb4adc646266bdc89cf179f4803a3dcb9030dd9e Mon Sep 17 00:00:00 2001 From: haelyra <49814733+haelyra@users.noreply.github.com> Date: Thu, 27 Aug 2026 20:02:55 -0400 Subject: [PATCH] docs: record plan canvas loading fix evidence --- docs/design/plan-canvas.md | 9 ++- docs/testing/plan-canvas-loading-hang.tdd.md | 81 ++++++++++++++++++++ 2 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 docs/testing/plan-canvas-loading-hang.tdd.md diff --git a/docs/design/plan-canvas.md b/docs/design/plan-canvas.md index 3de3fc733..4989b46fa 100644 --- a/docs/design/plan-canvas.md +++ b/docs/design/plan-canvas.md @@ -41,7 +41,7 @@ A loopback-only web editor for plan artifacts (and any local HTML artifact): | Server | `scripts/lib/plan-canvas/server.js` | control-pane loopback server, host-header + Origin allowlist (DNS-rebinding guard) | | Editor chrome | `scripts/lib/plan-canvas/ui.js` | `scripts/lib/control-pane/ui.js`, tokens from `scripts/dashboard-web.js` | | Markdown plan renderer | `scripts/lib/plan-canvas/markdown.js` | zero new deps; renders the `commands/plan.md` artifact schema (tables, tasks, code fences, Mermaid blocks) | -| Mermaid diagrams | `scripts/lib/plan-canvas/ui.js` | ` ```mermaid ` blocks render in the browser, themed to ECC; pinned CDN with offline fallback (`ECC_PLAN_CANVAS_MERMAID_URL` for a local mirror) | +| Mermaid diagrams | `scripts/lib/plan-canvas/ui.js` | ` ```mermaid ` blocks render in the browser after page load, themed to ECC; pinned CDN with non-blocking fallback (`ECC_PLAN_CANVAS_MERMAID_URL` for a local mirror) | | Session state | `scripts/lib/plan-canvas/sessions.js` | file-path-keyed sessions, state under `~/.claude/plan-canvas/` (`ECC_PLAN_CANVAS_STATE_DIR` override) | | Skill | `skills/plan-canvas/SKILL.md` | skills-first surface; teaches the open → await → reply loop; defers visual guidance to `frontend-design-direction`, `artifact-design`, `dataviz` | | Command shim | `commands/plan-canvas.md` | legacy parity surface, points at the skill | @@ -81,7 +81,7 @@ after 30 min, `ECC_PLAN_CANVAS_IDLE_MS`). Feedback is deliver-and-drain: queued handed to exactly one `await` call and persisted to disk until then, so nothing is lost if the poll is interrupted. -- `GET /health` — `{ok, app: "ecc-plan-canvas", version}` (CLI/server version handshake) +- `GET /health` — `{ok, app, version, protocolVersion, runtimeId}`; the CLI reuses a detached server only when its package, protocol, and Canvas-module fingerprint match, preventing an older same-version worktree from serving stale browser code - `GET /` — session list (ECC chrome) - `POST /api/sessions` `{file, reopen?}` — open/resume; `409 user-ended` unless `reopen` - `GET /canvas/` — editor chrome; `GET /artifact//` — rendered artifact @@ -115,6 +115,7 @@ sibling-asset access confined to the artifact directory; state dir is user-local never executes artifact content — it only serves it to the browser. The one optional outbound request is the pinned Mermaid library, fetched by the browser only -for artifacts that contain a diagram; it renders with `securityLevel: 'strict'`, degrades to -showing diagram source if unavailable, and can be repointed at a local mirror via +after an artifact containing a diagram has loaded; it renders with `securityLevel: 'strict'`, +cannot hold the Canvas page in a loading state, degrades to showing diagram source if +unavailable, and can be repointed at a local mirror via `ECC_PLAN_CANVAS_MERMAID_URL`. The server itself still makes no network calls. diff --git a/docs/testing/plan-canvas-loading-hang.tdd.md b/docs/testing/plan-canvas-loading-hang.tdd.md new file mode 100644 index 000000000..64916d84e --- /dev/null +++ b/docs/testing/plan-canvas-loading-hang.tdd.md @@ -0,0 +1,81 @@ +# Plan Canvas loading hang TDD evidence + +## Source + +No implementation plan was supplied. The journeys and guarantees below were +derived from the reported intermittent localhost loading hang, especially when +opening a second Plan Canvas in one agent chat. + +## User journeys + +1. As a reviewer opening plans from multiple ECC worktrees, I want every new + Canvas to use the current server code so an older same-version process cannot + disable or stall the page. +2. As a reviewer opening a plan with Mermaid diagrams, I want the Canvas page to + finish loading even when the Mermaid CDN is slow or unavailable. + +## Task report + +### Replace a stale same-version detached server + +- RED: `node tests/integration/plan-canvas-e2e.test.js` produced 9 passes and + 1 failure. The current CLI reused a fake legacy server that reported the same + package version and sent the session-open request to it. +- RED checkpoint: `5dc6d85c test: add reproducer for stale plan canvas server`. +- GREEN: the health handshake now carries a protocol version and a SHA-256 + fingerprint of every module loaded into the detached Canvas server. The CLI + retires any process whose package, protocol, or runtime fingerprint differs. +- GREEN: `node tests/integration/plan-canvas-e2e.test.js` produced 10 passes and + 0 failures. +- GREEN checkpoint: `7e0d115e fix: restart stale plan canvas servers`. + +### Keep Mermaid enhancement from blocking page load + +- RED: `node tests/scripts/plan-canvas.test.js` produced 27 passes and 1 + failure. The generated artifact loaded Mermaid with top-level `await`, which + allowed an unresolved remote import to hold the document load event open. +- RED checkpoint: `ca0d0415 test: reproduce mermaid page load stall`. +- GREEN: the dynamic Mermaid import now starts from an async `load` listener. + Browsers do not await that listener, so diagrams remain progressive + enhancement and the raw Mermaid source remains available during a network + stall. +- GREEN: `node tests/scripts/plan-canvas.test.js` produced 28 passes and 0 + failures. +- GREEN checkpoint: `7fc31254 fix: keep mermaid from blocking canvas load`. + +## Test specification + +| # | What is guaranteed | Test target | Type | Result | +| --- | --- | --- | --- | --- | +| 1 | A legacy server with the same package version is shut down before the current CLI opens a plan | `same-version legacy server is replaced before a canvas opens` | End to end | PASS | +| 2 | Health exposes package, protocol, and exact Canvas runtime identity | `GET /health identifies the app and version` | Integration | PASS | +| 3 | Mermaid remote enhancement starts only after document load | `a plan containing mermaid serves the themed Mermaid loader` | Integration | PASS | +| 4 | Open, browser load, await, feedback, reply, approval, reopen, end, and stop still work together | `tests/integration/plan-canvas-e2e.test.js` | End to end | PASS | +| 5 | The large ECC 2 to ECC 3 master plan bootstraps under blocked local storage | Headless Chrome DOM and screenshot check against `/canvas/24af75d4c4fe` | Browser | PASS | + +## Coverage and full-suite evidence + +`npm run coverage` passed all 3,993 discovered tests with these project totals: + +- Statements: 88.98% +- Branches: 80.66% +- Functions: 94.32% +- Lines: 88.98% + +The `scripts/lib/plan-canvas` group reached 98.38% statements, 88.2% branches, +98.64% functions, and 98.38% lines. Focused ESLint checks passed for every +modified JavaScript test and production file. + +## Browser evidence and known gaps + +The live shared server was initially process `15351`, started from the older +`ecc-tiered-sandbox` worktree, and served an unguarded `localStorage` client even +when invoked from current main. The patched CLI replaced it with the isolated +worktree server and restored persisted sessions. Two real plan sessions then +opened successfully. Headless Chrome with local storage disabled completed the +large master-plan DOM load in about two seconds and produced a rendered Canvas +screenshot. + +Chrome was exercised directly on macOS. Safari and Firefox were not run. The +fix relies only on standard health JSON, dynamic `import()`, and the standard +window `load` event.