agent-whiteboard 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (163) hide show
  1. package/CHANGELOG.md +470 -0
  2. package/LICENSE +21 -0
  3. package/README.md +313 -0
  4. package/bin/cli.js +30 -0
  5. package/dist/client/apple-touch-icon.png +0 -0
  6. package/dist/client/assets/KaTeX_AMS-Regular-0cdd387c.woff2 +0 -0
  7. package/dist/client/assets/KaTeX_AMS-Regular-30da91e8.woff +0 -0
  8. package/dist/client/assets/KaTeX_AMS-Regular-68534840.ttf +0 -0
  9. package/dist/client/assets/KaTeX_Caligraphic-Bold-07d8e303.ttf +0 -0
  10. package/dist/client/assets/KaTeX_Caligraphic-Bold-1ae6bd74.woff +0 -0
  11. package/dist/client/assets/KaTeX_Caligraphic-Bold-de7701e4.woff2 +0 -0
  12. package/dist/client/assets/KaTeX_Caligraphic-Regular-3398dd02.woff +0 -0
  13. package/dist/client/assets/KaTeX_Caligraphic-Regular-5d53e70a.woff2 +0 -0
  14. package/dist/client/assets/KaTeX_Caligraphic-Regular-ed0b7437.ttf +0 -0
  15. package/dist/client/assets/KaTeX_Fraktur-Bold-74444efd.woff2 +0 -0
  16. package/dist/client/assets/KaTeX_Fraktur-Bold-9163df9c.ttf +0 -0
  17. package/dist/client/assets/KaTeX_Fraktur-Bold-9be7ceb8.woff +0 -0
  18. package/dist/client/assets/KaTeX_Fraktur-Regular-1e6f9579.ttf +0 -0
  19. package/dist/client/assets/KaTeX_Fraktur-Regular-51814d27.woff2 +0 -0
  20. package/dist/client/assets/KaTeX_Fraktur-Regular-5e28753b.woff +0 -0
  21. package/dist/client/assets/KaTeX_Main-Bold-0f60d1b8.woff2 +0 -0
  22. package/dist/client/assets/KaTeX_Main-Bold-138ac28d.ttf +0 -0
  23. package/dist/client/assets/KaTeX_Main-Bold-c76c5d69.woff +0 -0
  24. package/dist/client/assets/KaTeX_Main-BoldItalic-70ee1f64.ttf +0 -0
  25. package/dist/client/assets/KaTeX_Main-BoldItalic-99cd42a3.woff2 +0 -0
  26. package/dist/client/assets/KaTeX_Main-BoldItalic-a6f7ec0d.woff +0 -0
  27. package/dist/client/assets/KaTeX_Main-Italic-0d85ae7c.ttf +0 -0
  28. package/dist/client/assets/KaTeX_Main-Italic-97479ca6.woff2 +0 -0
  29. package/dist/client/assets/KaTeX_Main-Italic-f1d6ef86.woff +0 -0
  30. package/dist/client/assets/KaTeX_Main-Regular-c2342cd8.woff2 +0 -0
  31. package/dist/client/assets/KaTeX_Main-Regular-c6368d87.woff +0 -0
  32. package/dist/client/assets/KaTeX_Main-Regular-d0332f52.ttf +0 -0
  33. package/dist/client/assets/KaTeX_Math-BoldItalic-850c0af5.woff +0 -0
  34. package/dist/client/assets/KaTeX_Math-BoldItalic-dc47344d.woff2 +0 -0
  35. package/dist/client/assets/KaTeX_Math-BoldItalic-f9377ab0.ttf +0 -0
  36. package/dist/client/assets/KaTeX_Math-Italic-08ce98e5.ttf +0 -0
  37. package/dist/client/assets/KaTeX_Math-Italic-7af58c5e.woff2 +0 -0
  38. package/dist/client/assets/KaTeX_Math-Italic-8a8d2445.woff +0 -0
  39. package/dist/client/assets/KaTeX_SansSerif-Bold-1ece03f7.ttf +0 -0
  40. package/dist/client/assets/KaTeX_SansSerif-Bold-e99ae511.woff2 +0 -0
  41. package/dist/client/assets/KaTeX_SansSerif-Bold-ece03cfd.woff +0 -0
  42. package/dist/client/assets/KaTeX_SansSerif-Italic-00b26ac8.woff2 +0 -0
  43. package/dist/client/assets/KaTeX_SansSerif-Italic-3931dd81.ttf +0 -0
  44. package/dist/client/assets/KaTeX_SansSerif-Italic-91ee6750.woff +0 -0
  45. package/dist/client/assets/KaTeX_SansSerif-Regular-11e4dc8a.woff +0 -0
  46. package/dist/client/assets/KaTeX_SansSerif-Regular-68e8c73e.woff2 +0 -0
  47. package/dist/client/assets/KaTeX_SansSerif-Regular-f36ea897.ttf +0 -0
  48. package/dist/client/assets/KaTeX_Script-Regular-036d4e95.woff2 +0 -0
  49. package/dist/client/assets/KaTeX_Script-Regular-1c67f068.ttf +0 -0
  50. package/dist/client/assets/KaTeX_Script-Regular-d96cdf2b.woff +0 -0
  51. package/dist/client/assets/KaTeX_Size1-Regular-6b47c401.woff2 +0 -0
  52. package/dist/client/assets/KaTeX_Size1-Regular-95b6d2f1.ttf +0 -0
  53. package/dist/client/assets/KaTeX_Size1-Regular-c943cc98.woff +0 -0
  54. package/dist/client/assets/KaTeX_Size2-Regular-2014c523.woff +0 -0
  55. package/dist/client/assets/KaTeX_Size2-Regular-a6b2099f.ttf +0 -0
  56. package/dist/client/assets/KaTeX_Size2-Regular-d04c5421.woff2 +0 -0
  57. package/dist/client/assets/KaTeX_Size3-Regular-500e04d5.ttf +0 -0
  58. package/dist/client/assets/KaTeX_Size3-Regular-6ab6b62e.woff +0 -0
  59. package/dist/client/assets/KaTeX_Size4-Regular-99f9c675.woff +0 -0
  60. package/dist/client/assets/KaTeX_Size4-Regular-a4af7d41.woff2 +0 -0
  61. package/dist/client/assets/KaTeX_Size4-Regular-c647367d.ttf +0 -0
  62. package/dist/client/assets/KaTeX_Typewriter-Regular-71d517d6.woff2 +0 -0
  63. package/dist/client/assets/KaTeX_Typewriter-Regular-e14fed02.woff +0 -0
  64. package/dist/client/assets/KaTeX_Typewriter-Regular-f01f3e87.ttf +0 -0
  65. package/dist/client/assets/arc-4691b008.js +1 -0
  66. package/dist/client/assets/architecture-7EHR7CIX-e08620e8.js +1 -0
  67. package/dist/client/assets/architectureDiagram-3BPJPVTR-9e2f2dbd.js +36 -0
  68. package/dist/client/assets/blockDiagram-GPEHLZMM-d9dd3f7a.js +132 -0
  69. package/dist/client/assets/bootstrap.min-21652813.js +6 -0
  70. package/dist/client/assets/c4Diagram-AAUBKEIU-4ed97d79.js +10 -0
  71. package/dist/client/assets/channel-9d07a35d.js +1 -0
  72. package/dist/client/assets/chunk-2J33WTMH-8049adc8.js +1 -0
  73. package/dist/client/assets/chunk-4BX2VUAB-fd7396be.js +1 -0
  74. package/dist/client/assets/chunk-55IACEB6-ae1ac93c.js +1 -0
  75. package/dist/client/assets/chunk-727SXJPM-7e3658d9.js +206 -0
  76. package/dist/client/assets/chunk-AQP2D5EJ-114f64b0.js +231 -0
  77. package/dist/client/assets/chunk-FMBD7UC4-f2647c5e.js +15 -0
  78. package/dist/client/assets/chunk-ND2GUHAM-0a2fcd8c.js +1 -0
  79. package/dist/client/assets/chunk-QZHKN3VN-d00b3f33.js +1 -0
  80. package/dist/client/assets/classDiagram-4FO5ZUOK-ebb5cc4a.js +1 -0
  81. package/dist/client/assets/classDiagram-v2-Q7XG4LA2-ebb5cc4a.js +1 -0
  82. package/dist/client/assets/colors-98f238a0.js +1 -0
  83. package/dist/client/assets/cose-bilkent-S5V4N54A-1f808fd7.js +1 -0
  84. package/dist/client/assets/cytoscape.esm-1491e259.js +331 -0
  85. package/dist/client/assets/dagre-BM42HDAG-e8ec1c9d.js +4 -0
  86. package/dist/client/assets/defaultLocale-a8476f12.js +1 -0
  87. package/dist/client/assets/diagram-2AECGRRQ-83b385bf.js +43 -0
  88. package/dist/client/assets/diagram-5GNKFQAL-d9406c6a.js +10 -0
  89. package/dist/client/assets/diagram-KO2AKTUF-3db5b95a.js +3 -0
  90. package/dist/client/assets/diagram-LMA3HP47-fbd90397.js +24 -0
  91. package/dist/client/assets/diagram-OG6HWLK6-3c697534.js +24 -0
  92. package/dist/client/assets/embed-2b4adf34.js +201 -0
  93. package/dist/client/assets/erDiagram-TEJ5UH35-7f41448f.js +85 -0
  94. package/dist/client/assets/eventmodeling-FCH6USID-e6d9019e.js +1 -0
  95. package/dist/client/assets/flowDiagram-I6XJVG4X-38f05cc7.js +162 -0
  96. package/dist/client/assets/ganttDiagram-6RSMTGT7-39287f5a.js +292 -0
  97. package/dist/client/assets/gitGraph-WXDBUCRP-037e225a.js +1 -0
  98. package/dist/client/assets/gitGraphDiagram-PVQCEYII-3c2e3ef7.js +106 -0
  99. package/dist/client/assets/graph-f3dfaf53.js +1 -0
  100. package/dist/client/assets/index-4abf3da0.js +5 -0
  101. package/dist/client/assets/index-a1656aec.js +1 -0
  102. package/dist/client/assets/index-d674a6a4.css +1 -0
  103. package/dist/client/assets/info-J43DQDTF-51cd3390.js +1 -0
  104. package/dist/client/assets/infoDiagram-5YYISTIA-ef2826b7.js +2 -0
  105. package/dist/client/assets/init-a5b10ee5.js +1 -0
  106. package/dist/client/assets/ishikawaDiagram-YF4QCWOH-d0b8a2e0.js +70 -0
  107. package/dist/client/assets/journeyDiagram-JHISSGLW-586ffcf8.js +139 -0
  108. package/dist/client/assets/kanban-definition-UN3LZRKU-9e58e14f.js +89 -0
  109. package/dist/client/assets/katex-0cba7652.js +257 -0
  110. package/dist/client/assets/katex-4c9ea27f.js +257 -0
  111. package/dist/client/assets/layout-3a4eff80.js +1 -0
  112. package/dist/client/assets/linear-fed00341.js +1 -0
  113. package/dist/client/assets/mermaid-parser.core-7ee09942.js +173 -0
  114. package/dist/client/assets/mermaid.core-9fc8f6d2.js +306 -0
  115. package/dist/client/assets/mindmap-definition-RKZ34NQL-a40dd09f.js +96 -0
  116. package/dist/client/assets/ordinal-db88ae6f.js +1 -0
  117. package/dist/client/assets/packet-YPE3B663-f55b568a.js +1 -0
  118. package/dist/client/assets/pie-LRSECV5Y-88c1a5e6.js +1 -0
  119. package/dist/client/assets/pieDiagram-4H26LBE5-51aae5ea.js +30 -0
  120. package/dist/client/assets/quadrantDiagram-W4KKPZXB-bc4686f9.js +7 -0
  121. package/dist/client/assets/radar-GUYGQ44K-6ede145d.js +1 -0
  122. package/dist/client/assets/range-6bc2a9a0.js +1 -0
  123. package/dist/client/assets/requirementDiagram-4Y6WPE33-8466f5a1.js +84 -0
  124. package/dist/client/assets/sankeyDiagram-5OEKKPKP-fd54c1f6.js +40 -0
  125. package/dist/client/assets/sequenceDiagram-3UESZ5HK-baa63c66.js +162 -0
  126. package/dist/client/assets/stateDiagram-AJRCARHV-bb4f0d0b.js +1 -0
  127. package/dist/client/assets/stateDiagram-v2-BHNVJYJU-9479cd12.js +1 -0
  128. package/dist/client/assets/step-171ad54a.js +1 -0
  129. package/dist/client/assets/time-043b3464.js +1 -0
  130. package/dist/client/assets/timeline-definition-PNZ67QCA-f64b58bd.js +120 -0
  131. package/dist/client/assets/treeView-BLDUP644-73ba1a83.js +1 -0
  132. package/dist/client/assets/treemap-LRROVOQU-98e47821.js +1 -0
  133. package/dist/client/assets/vennDiagram-CIIHVFJN-a410b1f7.js +34 -0
  134. package/dist/client/assets/wardley-L42UT6IY-a27badb9.js +1 -0
  135. package/dist/client/assets/wardleyDiagram-YWT4CUSO-527ffe52.js +78 -0
  136. package/dist/client/assets/xychartDiagram-2RQKCTM6-42b5914b.js +7 -0
  137. package/dist/client/favicon-16x16.png +0 -0
  138. package/dist/client/favicon-32x32.png +0 -0
  139. package/dist/client/favicon.ico +0 -0
  140. package/dist/client/icon-192.png +0 -0
  141. package/dist/client/icon-512.png +0 -0
  142. package/dist/client/index.html +18 -0
  143. package/dist/server/app.js +429 -0
  144. package/dist/server/channel.js +42 -0
  145. package/dist/server/events.js +90 -0
  146. package/dist/server/export-html.js +461 -0
  147. package/dist/server/index.js +79 -0
  148. package/dist/server/interaction.js +135 -0
  149. package/dist/server/mcp.js +421 -0
  150. package/dist/server/paths.js +11 -0
  151. package/dist/server/persist.js +56 -0
  152. package/dist/server/presentation.js +5 -0
  153. package/dist/server/render-core.js +213 -0
  154. package/dist/server/session.js +103 -0
  155. package/dist/server/slideshow.js +83 -0
  156. package/dist/server/snapshot-reader.js +190 -0
  157. package/dist/server/snapshot-writer.js +117 -0
  158. package/dist/server/snapshot.js +47 -0
  159. package/dist/server/step-frames-builder.js +70 -0
  160. package/dist/server/validate.js +140 -0
  161. package/dist/server/viewport-cache.js +69 -0
  162. package/dist/server/ws.js +71 -0
  163. package/package.json +92 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,470 @@
1
+ ## 1.0.0 — 2026-07-19
2
+
3
+ **Milestone v1.0 — 1.0 Release Readiness & npx Distribution (Sprints 75–78) complete.** The first public release: installable and runnable via `npx agent-whiteboard`, with a self-contained production entrypoint, publish-ready package metadata, an enforced local-only trust model, a CI pipeline, and a fix for the one HIGH-severity accessibility finding from the pre-1.0 review pass (FR28 in `01`, F27–F28/NF33–NF37 in `03`).
4
+
5
+ - **Sprint 75 (NF33/NF34):** `createApp()` gains an opt-in `staticRoot` option that serves the built `dist/client` directly via `@hono/node-server/serve-static`, mounted last so it never shadows an API route — dev mode (`npm run dev`, Vite dev server + proxy) is unaffected. `server/index.ts`'s startup logic is extracted into an exported `startServer({ staticRoot, onReady })`, guarded behind an is-main-module check so it can be called as a library without double-starting. New `bin/cli.js` (plain ESM, no build step) is the production entrypoint `npx agent-whiteboard` runs — resolves `dist/client` relative to its own file location (cwd-independent) and opens the browser via the existing `open` dependency, importing nothing outside `dependencies`.
6
+ - **Sprint 76 (F27/F28):** `startServer()` now refuses to bind a non-loopback `HOST` unless `ALLOW_NON_LOOPBACK=1` is explicitly set, turning the accepted single-user/local-only trust model into an enforced guarantee instead of a documentation-only default. `NodeActionPopup.svelte`'s keyboard trap (no `Escape`-reachable dismiss path — HIGH finding in `docs/06_frontend_review.md`) is fixed by reusing the existing `trapFocus` action, and its action items now activate on `Space` as well as `Enter`.
7
+ - **Sprint 77 (NF35/NF36):** `package.json` gains `license`, `description`, `repository`, `homepage`, `bugs`, `author`, `keywords`, a `bin` field (`agent-whiteboard` → `bin/cli.js`), and a `files` allowlist (`bin`, `dist`, `README.md`, `LICENSE`, `CHANGELOG.md`) — `private: true` is removed and `version` becomes `1.0.0`. `npm pack --dry-run` now ships 163 files (down from 213 unfiltered) and `npm publish --dry-run` succeeds. `README.md` gains an `npx agent-whiteboard` quickstart and an explicit trust-model statement.
8
+ - **Sprint 78 (NF37):** new `.github/workflows/ci.yml` runs `npm ci`, typecheck, lint, test, and build on every push/PR to `master` — no deploy/publish automation, publishing stays a manual, deliberate step.
9
+ - **Deferred (Design Debt Log in `01`):** all MEDIUM-severity findings from the Node.js/TS and Svelte/TS review passes (body-size limits, WS auto-reconnect, `export-html`'s global-state serialization, etc.) — deliberately out of scope for this milestone, logged as candidates for a future one.
10
+ - Full suite: 525 unit tests passing (up from 513 pre-milestone), `tsc --noEmit`/`svelte-check`/`eslint` clean throughout.
11
+
12
+ ## 0.28.0 — 2026-07-18
13
+
14
+ **Milestone v0.33 — Dark Theme & Theme Selector (Sprint 74) complete.** The board chrome (toolbar, history panel, delete/export modal) now supports a Light/Dark theme, toggled from the toolbar and persisted across sessions — rendered content on the canvas is explicitly untouched (FR27 in `01`, F24–F26 in `03`).
15
+
16
+ - **`client/src/theme.css`:** board-chrome color custom properties (`--board-*`) defined for `:root[data-theme="light"]` and `:root[data-theme="dark"]`; a dedicated `--board-canvas-bg` keeps the canvas itself light grey even in dark mode, since rendered content isn't themed and generally assumes a light backdrop.
17
+ - **`client/src/stores/themeStore.ts`:** persists the selected theme to `localStorage`, defaults to Light when nothing is stored, and applies it via a `data-theme` attribute on `<html>`.
18
+ - **`App.svelte`, `HistoryPanel.svelte`, `DeleteExportModal.svelte`, `SnapshotRow.svelte`:** hardcoded hex colors swept to `var(--board-*)` references; a sun/moon toggle button added to the toolbar's controls-panel, bound to `themeStore`.
19
+ - **Isolation (F26):** no server/export-pipeline changes — rendered content (`Html.svelte`/`Mermaid.svelte`, `export-html` in both `cdn` and `offline` modes) never reads board theme variables. A static test (`themeIsolation.test.ts`) asserts every declared custom property is namespaced `--board-` and that no renderer source references one.
20
+ - Full suite: 513 unit tests passing (up from 507 pre-milestone), `tsc --noEmit`/`svelte-check` clean throughout.
21
+
22
+ ## 0.27.5 — 2026-07-18
23
+
24
+ **Milestone v0.32 — Export Delivery Model: CDN Default & Filesystem Boundary (Sprint 73) complete.** HTML export switches its default dependency-loading from fully embedded to CDN-linked, and the MCP `export_html` tool drops its `output_path` parameter entirely — closing a way for the agent to write to an arbitrary filesystem path that could bypass a write-only-within-project-folders guardrail (FR26 in `01`, O1–O4 in `02`, F21–F23 in `03`).
25
+
26
+ - **CDN mode (`server/export-html.ts`):** Mermaid, Bootstrap, and KaTeX are now referenced via pinned-version, SRI-hashed jsdelivr `<script>`/`<link>` tags instead of being embedded — `generateExportHtml()` takes an explicit `mode: "cdn" | "offline"`. The SRI hash is computed at runtime from the locally installed dist file (same version jsdelivr serves), so it can never drift out of sync on a dependency bump — no manual regeneration step needed. `offline` mode reproduces the pre-v0.32 fully-embedded behavior byte-for-byte. The export's CSP is widened to allow `cdn.jsdelivr.net` for `script-src`/`style-src` only in `cdn` mode.
27
+ - **MCP `export_html(workspace, ids)`:** always runs in `cdn` mode and returns the assembled HTML inline as a string in the tool response — no filesystem access, no `output_path` parameter, never writes to disk. `writeExportHtmlToDisk()` is removed (no other caller).
28
+ - **REST `POST /export-html`:** gains an optional `mode: "cdn" | "offline"` body field (default `"cdn"`, any unrecognized value also falls back to `"cdn"`); response streaming (`Content-Disposition: attachment`, no server-side write) is unchanged in either mode.
29
+ - **Accepted trade-off (O4 in `02`):** Bootstrap's `@scope`-based leak fix (bug B20, v0.31) only works when its CSS is inlined — a CDN `<link>` can't be scope-wrapped. `cdn` mode links Bootstrap unscoped anyway rather than keeping it inlined; `offline` mode is unaffected.
30
+ - Full suite: 507 unit tests passing, `tsc --noEmit`, `svelte-check`, `npm run build`, and `npm run lint` clean throughout.
31
+
32
+ ## 0.27.4 — 2026-07-18
33
+
34
+ **Milestone v0.31 — Bootstrap House Style for HTML Content (Sprints 69–72) complete.** `type: "html"` payloads can now use Bootstrap 5 (CSS-only) component classes instead of hand-authored CSS, in both the live canvas and HTML exports (FR25 in `01`, F20 in `03`).
35
+
36
+ - **Sprint 69:** added the `bootstrap` npm dependency (CSS-only, no JS import); generalized `export-html.ts`'s `scopeEmbeddedStyles()` into a reusable `scopeCss(css, anchorIds[])` primitive that wraps a CSS string in `@scope (<selector-list>) { ... }`, supporting a comma-separated multi-anchor list for one shared stylesheet scoped across every relevant anchor in a single export
37
+ - **Sprint 70:** `assembleHtml()` reads `bootstrap.min.css` at export-assembly time and includes it in the export's `<style>` block only when the export contains ≥1 `html`-type item (plain or step-frames frame), scoped via `scopeCss()` to every such item/frame's own anchor id
38
+ - **Sprint 71:** `Html.svelte` lazy-loads the same stylesheet only when `type === "html"` mounts (never for `"svg"`), via Vite's `?inline` CSS import, scoped to its own container with a client-side `scopeCss` helper (duplicated from the server's — no shared client/server module in this codebase)
39
+ - **Bug found via manual browser verification, fixed in both Sprint 70 (retroactively) and Sprint 71:** `@scope` only matches elements within the scope root's own subtree, so Bootstrap's `:root`-scoped CSS custom properties (its entire color system) silently never applied — only non-variable properties like padding worked, with no error surfaced. Fixed by rewriting `:root` → `:scope` in the Bootstrap CSS text before wrapping (`:scope` inside an `@scope` block refers to the scope root element itself, and properties set there inherit normally to every descendant). Verified live via Playwright/Chromium screenshots before and after the fix; documented as a resolved risk in `02` (L7)
40
+ - **Sprint 72:** the `render` MCP tool's `"html"` type description now names Bootstrap 5 explicitly, gives concrete class examples (`card`, `alert alert-info`, `badge`, `table table-striped`), and states the CSS-only/no-JS-components caveat (dropdowns, modals, tooltips, popovers, collapses, carousels, offcanvas render static markup only)
41
+ - Added a Bootstrap house style slide to the manual showcase (`tests/human_driven/showcase.js`), alongside the existing plain-HTML slide
42
+ - Full suite: 499 unit tests passing (up from 491 pre-milestone), `tsc --noEmit` (server) and `svelte-check` (client) clean throughout. `03` F20, `02` L7, and `04`'s dependency table/export pseudocode/`Html.svelte` tree note moved from planned to resolved/shipped
43
+
44
+ ## 0.27.3 — 2026-07-15
45
+
46
+ **Milestone v0.30 — Export HTML Layout & Navigation Fixes (Sprints 66–68) complete.** User-reported export bugs, all in `server/export-html.ts`'s assembled document (F17 in `03`, B20–B22 in `01`): a narrow `<main>` column on wide screens with tables/code blocks bleeding past their section border, misaligned table right borders, and no way to jump to a specific frame of a step-frames export from the left-menu nav.
47
+
48
+ - **Sprint 66 (B20):** `.item-section`/`.frame-section` gained `overflow-x: auto` so an oversized table/code block scrolls within its own section instead of overflowing the page — a real fix, but manual testing showed it wasn't the actual cause of the "narrow main" report. The real cause: an `html`-type payload's own embedded `<style>` tag (e.g. a markdown→HTML theme's `body { max-width: 900px }`) is document-scoped, not scoped to its `.item-section`, and was silently overriding the whole export's layout. An initial `FORBID_TAGS: ["style"]` fix closed the leak but also discarded the payload's own legitimate table/code formatting living in the same `<style>` block; replaced with `scopeEmbeddedStyles()`, which wraps any surviving `<style>` tag's content in `@scope (#<item-or-frame-id>) { ... }` — containing selector *matching* to that item/frame's own subtree without discarding anything
49
+ - **Sprint 67 (B21):** `table` gained `border-collapse: collapse`, removing the default `border-collapse: separate` + `border-spacing` gap that visibly offset a table's own outer border from its cells' borders
50
+ - **Sprint 68 (B22):** `assembleHtml()`'s TOC now expands a step-frames item into one nav submenu link per frame (`<ul class="toc-frames">`), titled with the frame's own label (falling back to "Frame N"); the item's own (parent) link points at frame 0's anchor, matching `<main>`'s existing per-frame sub-sections
51
+ - All three root-caused and verified via direct Playwright reproduction (headless Chromium) before and after each fix, in addition to new unit test coverage in `tests/unit/server/export-html.test.ts`
52
+ - Full suite: 489 unit tests passing (up from 486 pre-milestone), 38/38 e2e tests, `tsc --noEmit` and `npm run lint` clean throughout. `03` §5d and `04`'s HTML Export data-flow notes updated from gap to resolved
53
+
54
+ ## 0.27.2 — 2026-07-12
55
+
56
+ **Milestone v0.29 — Client Component Responsibility Cleanup (Sprints 62–65) complete.** A direct review of the four largest `client/src/` files (`docs/06_frontend-desing-review.md`, `01` FR24) found the same "responsibilities hidden inside one file" pattern `Milestone_v0.28.md` cleaned up server-side (`02` §N7), one tier over in the browser components (`02` §N8). All four items landed as output-equivalence refactors — no user-visible behavior change.
57
+
58
+ - **Sprint 62 (NF29 part 1):** the pan/zoom camera (`scale`/`tx`/`ty`/`dragging` state, `fitToView`, `applyViewport`, wheel/mouse handlers, `resetTransform`, debounced `/viewport` reporting) extracted out of `renderers/Mermaid.svelte` into `renderers/mermaid/panZoom.ts` as a standalone Svelte-store-backed module
59
+ - **Sprint 63 (NF29 part 2 + NF30):** click-to-server-action routing (`attachClickListeners`/`detachClickListeners`, `onNodeClick`/`onEdgeClick`, `POST /node-click`) and node-to-frame navigation wiring (`attachNodeToFrameListeners`/`detachNodeToFrameListeners`, `POST /seek`) extracted into `renderers/mermaid/nodeInteractions.ts`; the node-action popup menu (markup + styles) extracted into `renderers/mermaid/NodeActionPopup.svelte`, with only its open/closed state remaining in `Mermaid.svelte`
60
+ - **Sprint 64 (NF31):** `formatTimestamp()`, byte-identical in `HistoryPanel.svelte` and `DeleteExportModal.svelte`, unified into `lib/formatTimestamp.ts`; the snapshot title/type-badge/timestamp display (markup + CSS, also duplicated between the same two files) unified into `lib/SnapshotRow.svelte`, used by both
61
+ - **Sprint 65 (NF32):** `DeleteExportModal.svelte`'s inline fetch logic for `/snapshots/delete-workspace`, `/snapshots/delete-files`, and `/export-html` (including response parsing and error-message extraction) moved into `deleteWorkspace`/`deleteFiles`/`exportItems` in a new `lib/snapshotActions.ts` — the component now contains no direct `fetch()` calls, only step/UI orchestration
62
+ - `renderers/Mermaid.svelte` shrank from 580 lines to just the Mermaid source→SVG rendering pipeline and Mermaid-DOM-parsing helpers (`extractNodeId`/`extractNodeLabel`/`extractEdgeId`); no change to `renderers/registry.ts` or any external API/WebSocket contract
63
+ - Full suite: 484 unit tests passing (up from 457 pre-milestone — new coverage in `panZoom.test.ts`, `nodeInteractions.test.ts`, `NodeActionPopup.test.ts`, `formatTimestamp.test.ts`, `SnapshotRow.test.ts`, `snapshotActions.test.ts`), 38/38 e2e tests, `tsc --noEmit` (client) and `npm run lint` clean throughout. `02` §N8, `03` §10, `04` §9.8 updated from open/scheduled to resolved
64
+
65
+ ## 0.27.1 — 2026-07-10
66
+
67
+ **Milestone v0.28 — `app.ts` Responsibility Cleanup (Sprints 58–61) complete.** A follow-up manual review (`01` FR23, `02` §N7) found that v0.27's REST/MCP parity work, being scoped to REST↔MCP drift, couldn't see responsibilities misplaced *within* `app.ts` itself — duplication inside one transport's file, not between transports. This milestone cleaned up the four in-scope findings; item 5 (replacing string-matched error status derivation with a neutral `{ok,error,category?}` shape) remains explicitly deferred to a future intake.
68
+
69
+ - **Sprint 58 (NF25):** `/snapshots/load`'s validate → set-canvas-state → broadcast → track-workspace sequence extracted into `applyLoadedSnapshotResult()` in `render-core.ts`, joining `commitRenderResult`/`commitStepFramesResult` (persist-trigger `never`)
70
+ - **Sprint 59 (NF26):** `server/snapshot.ts` renamed to `server/snapshot-writer.ts`; `deleteSnapshotFiles()`/`deleteWorkspace()` and `validateWorkspaceForDelete()` moved out of `app.ts`'s delete handlers into the renamed module, paired with `snapshot-reader.ts`
71
+ - **Sprint 60 (NF27):** `/snapshots/load`'s and `/export-html`'s remaining ad-hoc workspace checks consolidated onto `validateWorkspaceInput()`
72
+ - **Sprint 61 (NF28):** the snapshot-filename safety regex, copy-pasted in two places, became one exported `isValidSnapshotFilename()` helper
73
+ - Architectural note: `validateWorkspaceInput()` and `isValidSnapshotFilename()` ended up living in `validate.ts` rather than `render-core.ts`/`snapshot-writer.ts` as originally sketched — `snapshot-writer.ts` needing `validateWorkspaceInput()` would otherwise have cycled back through `render-core.ts`'s existing `generateSnapshotId()` import, which deadlocked Vitest's `vi.mock`/`importOriginal()` during test collection. `validate.ts` is a true leaf module, so both can depend on it one-directionally
74
+ - Full suite: 444 unit tests, 38 e2e tests, `tsc --noEmit` (server+client), and `npm run lint` clean throughout. `02` §N7, `03` §9, `04` §9.7 updated from open/scheduled to resolved
75
+
76
+ ## 0.27.0 — 2026-07-09
77
+
78
+ **Milestone v0.27 — REST/MCP Parity Remediation (Sprints 51–57) complete.** A follow-up REST↔MCP duplication audit (`docs/raw/design-problems.md`, findings F1–F7) found that the v0.23 unified projector (NF14) and the rest of the v0.23–v0.26 architecture consolidation closed transport drift only for the commands already migrated to `render-core.ts` at the time each slice shipped. This milestone finished the job.
79
+
80
+ - **Sprint 51 (F1/NF18):** MCP's `slideshow` tool now routes every slide through `validate.ts`'s `validateFrame()` instead of hand-rolling its own checks
81
+ - **Sprint 52 (F2/NF19):** `step`/`seek` business logic extracted into `render-core.ts` (`stepAndBroadcast`/`seekAndBroadcast`), joining `render`/step-frames as commands that structurally cannot drift between transports
82
+ - **Sprint 53 (F3/NF20):** `GET /snapshots` requires `workspace` with no `lastWorkspace` fallback, matching MCP's `list_snapshots` exactly
83
+ - **Sprint 54 (F4/NF21):** `export-html` unifies on `ids` on both transports; the browser's export flow resolves selections to snapshot ids instead of filenames
84
+ - **Sprint 55 (F5/NF22):** new `server/paths.ts`'s `getSnapshotsRoot()` replaces 10 independent reimplementations of the snapshots-root expression (one more than originally audited — `migrate-snapshots.ts`'s CLI default was a bonus find)
85
+ - **Sprint 56 (F6/NF23):** `app.ts` reuses `snapshot-reader.ts`'s `isFrameArray()` instead of a third inline re-definition
86
+ - **Sprint 57 (F7/NF24):** `node_actions`/`node_to_frame` shape validation shared via zod schemas (`validate.ts`) on both transports, replacing REST's hand-written type guards
87
+ - Full suite: 463 unit tests passing (up from 461 pre-milestone), `tsc --noEmit`, `svelte-check`, and `npm run lint` clean throughout. `02` §N6, `03` §8, `04` §9.6, and the `04` §9.4 B6 traceability caveat updated from scheduled/gap to resolved
88
+
89
+ ## 0.26.8 — 2026-07-09
90
+
91
+ **Milestone v0.27 — REST/MCP Parity Remediation, Sprint 56.** The "is this a valid Frame[]" predicate was reimplemented inline in `app.ts`'s `/snapshots/load` handler instead of reusing the existing `isFrameArray()` in `snapshot-reader.ts` (F6). The `export-html` copy of this same duplication was already removed incidentally by NF21 (Sprint 54).
92
+
93
+ - `isFrameArray()` is now exported from `snapshot-reader.ts` and used directly in `app.ts`, replacing the inline predicate and its separate array/non-empty check with one call
94
+ - Removed the now-fully-unused `Frame` type import from `app.ts`. No behavior change
95
+ - Full suite: 463 unit tests passing, `tsc --noEmit` and `npm run lint` clean
96
+
97
+ ## 0.26.7 — 2026-07-09
98
+
99
+ **Milestone v0.27 — REST/MCP Parity Remediation, Sprint 55.** `WHITEBOARD_SNAPSHOTS_DIR ?? join(homedir(), ".agent-whiteboard")` was reimplemented independently 10 times across `app.ts`, `mcp.ts`, `snapshot.ts`, `viewport-cache.ts`, and `migrate-snapshots.ts` — no module exported a single canonical function (F5).
100
+
101
+ - New `server/paths.ts` exports `getSnapshotsRoot()`, now the one implementation. `app.ts`'s closure-local `resolveSnapshotRoot()` and `viewport-cache.ts`'s private `snapshotsRoot()` wrappers are deleted in favor of calling it directly
102
+ - Bonus fix found during implementation: `migrate-snapshots.ts`'s CLI `--dir` default fallback had the same root cause but wasn't in the original audit — fixed for consistency
103
+ - No behavior change. Full suite: 463 unit tests passing (461 + 2 new, covering the new module directly), `tsc --noEmit` and `npm run lint` clean
104
+
105
+ ## 0.26.6 — 2026-07-09
106
+
107
+ **Milestone v0.27 — REST/MCP Parity Remediation, Sprint 54.** REST's `/export-html` accepted export items as either `{workspace, filename}` or `{workspace, id}`; MCP's `export_html` only accepted `ids` — an undocumented feature asymmetry between the two transports (F4).
108
+
109
+ - The server already sent `id` in every `/snapshots/all` entry — this was purely a client-side gap. `SnapshotEntry` gained `id`, and `DeleteExportModal.svelte`'s export flow ("whole workspace" and "selected subset") now resolves each selection to its `id` before building the request instead of sending `filename`
110
+ - `app.ts`'s ~25-line filename-lookup branch (JSON parse + inline Frame-array validation) is removed — both transports now go through the same `findSnapshotByIdInWorkspace()` path
111
+ - Snapshots without an `id` (pre-migration only — every snapshot written since the v0.26 schema migration always has one) are skipped client-side, with an error if that empties the export set
112
+ - Full suite: 461 unit tests passing, `tsc --noEmit`, `svelte-check`, and `npm run lint` clean. Verified live: exporting a workspace (whole and selected subset) via the History panel downloads a working HTML file
113
+
114
+ ## 0.26.5 — 2026-07-09
115
+
116
+ **Milestone v0.27 — REST/MCP Parity Remediation, Sprint 53.** `GET /snapshots` (REST) fell back to `getLastWorkspace()` when `workspace` was omitted; MCP's `list_snapshots` already required it with no fallback — the same conceptual operation had different observable behavior depending on transport (F3).
117
+
118
+ - Audited the browser's actual call sites first: `HistoryPanel.svelte` never calls bare `GET /snapshots` (only `/snapshots/all`, `/snapshots/load`, and the two delete endpoints), so removing the fallback has zero browser-visible surface area — `README.md`'s "used by the browser History panel" line for this endpoint was already stale and is corrected
119
+ - `GET /snapshots` now goes through the same `validateWorkspaceInput()` that `render()`/`slideshow()`/`list_snapshots()` already use, returning `{ok:false,error:"workspace is required"}` (400) for a missing or empty workspace, exactly matching MCP's `list_snapshots`
120
+ - Full suite: 461 unit tests passing (1:1 test swap — no new regression surface), `tsc --noEmit` and `npm run lint` clean
121
+
122
+ ## 0.26.4 — 2026-07-09
123
+
124
+ **Milestone v0.27 — REST/MCP Parity Remediation, Sprint 52.** `step`/`seek`'s business logic was copied verbatim between `app.ts` and `mcp.ts` (F2) instead of living in `render-core.ts` alongside `render`/`init_step_frames`/`append_frame`/`commit_step_frames` — the one hot-path pair `render-core.ts`'s own header comment (NF12, "the two transports can never drift") claimed was already covered.
125
+
126
+ - New `stepAndBroadcast(direction)`/`seekAndBroadcast(frame)` in `render-core.ts` are now the single implementation of the viewport-lookup + `resolvedId` + broadcast logic behind `step`/`seek`
127
+ - `POST /step`/`POST /seek` (`app.ts`) and `step()`/`seek()` (`mcp.ts`) now only handle request-shape validation before calling the shared functions — no independent business logic remains in either file; several now-unused imports removed from both
128
+ - No behavior change — output-equivalence verified by the full existing `app.test.ts`/`mcp.test.ts` step/seek coverage (461 unit tests total) passing unchanged, `tsc --noEmit` and `npm run lint` clean
129
+
130
+ ## 0.26.3 — 2026-07-09
131
+
132
+ **Milestone v0.27 — REST/MCP Parity Remediation, Sprint 51.** A follow-up REST↔MCP duplication audit (`docs/raw/design-problems.md`, F1–F7) found the v0.23 unified projector (NF14) closed drift only for the commands already routed through `render-core.ts` at the time each slice shipped — `slideshow` validation was one of the gaps left over.
133
+
134
+ - MCP's `slideshow` tool hand-rolled its own mermaid/vega-lite checks instead of calling `validate.ts`'s `validateFrame()`, violating that function's own documented "no second implementation" invariant and silently diverging from REST's `/slideshow` handler on any future validation change (F1)
135
+ - The 44-line hand-rolled per-type validation loop in `mcp.ts`'s `slideshow` tool is replaced with a single `validateFrame()` call per slide, matching `app.ts`'s `/slideshow` handler's `slide[i]: <error>` format exactly; `hasMermaidKeyword`/`parseMermaid` imports removed from `mcp.ts` (no longer used directly)
136
+ - New unit tests: an invalid vega-lite slide (exact error-message parity with REST) and a multi-slide case asserting the correct `slide[i]` index on a non-zero failing slide. Full suite: 461 unit tests passing (up from 459), `tsc --noEmit` and `npm run lint` clean
137
+
138
+ ## 0.26.2 — 2026-07-09
139
+
140
+ **Milestone v0.26.1 — Node-to-Frame Broadcast Fix (patch), Sprint 50.** Reverses the pre-v0.26.1 behavior where a step-frames sequence computed its fit-to-view once at frame 0 and held it for the whole sequence — frames of very different intrinsic size (e.g. a tall sequence diagram followed by a wide flowchart) could overflow or under-fill (bug B19, the already-decided but unscheduled FR21).
141
+
142
+ - `viewport-cache.ts`'s `getViewport`/`setViewport`/`deleteViewports` move from a bare snapshot `id` key to a composite `id:frameIndex` key, so each frame persists its own manual viewport independently
143
+ - `broadcastStepFrames()` gains a `viewport` parameter; every frame-navigation path (`POST /step`, `POST /seek`, `step()`/`seek()` MCP tools) now looks up and forwards a per-frame viewport — previously only history-reload did this at all. `POST /viewport`'s body gains a required `frame` field
144
+ - `Mermaid.svelte` gains a `currentFrame` prop (threaded through `App.svelte` → `registry.ts`); `isNewSnapshot()` now compares a composite `"<id>:<frame>"` key instead of `id` alone, so navigating to a different frame of an already-seen sequence triggers a fresh fit/restore instead of a no-op continuation
145
+ - Rewrote the e2e test that asserted the old "preserves the live zoom/pan across frames" behavior into one asserting per-frame independence. Full suite: 459 unit tests passing (up from 444), e2e 37/38 (the one failure is the pre-existing dev-server-startup race documented in prior sprints), `tsc --noEmit` (server + client) and `npm run lint` clean. Verified live: zooming one frame, navigating to a differently-sized frame gets its own fresh fit, navigating back restores the original frame's manual viewport
146
+
147
+ ## 0.26.1 — 2026-07-09
148
+
149
+ **Milestone v0.26.1 — Node-to-Frame Broadcast Fix (patch), Sprint 49.** Bugfix-only patch release: `server/ws.ts`'s `broadcastStepFrames()` had no `nodeToFrame` parameter and never forwarded it into `broadcastReplace()`, so a live `commit_step_frames(id, node_to_frame={...})` call silently dropped the node-click map — clicking a mapped node in the browser did nothing, even though the map was correctly computed and persisted (bug B18).
150
+
151
+ - Tracing every call site of the wrapper found the same drop in `POST /step` and the `step()` MCP tool too — neither forwarded `state.nodeToFrame`, so a mapped-node click would stop working the moment the sequence advanced via Prev/Next even after fixing the commit path alone. `/seek` and `seek()` were already correct (they call `broadcastReplace()` directly)
152
+ - `broadcastStepFrames()` gained an optional 6th `nodeToFrame` parameter, threaded through all three affected call sites (`commitStepFramesResult()`, `POST /step`, `step()`)
153
+ - New unit tests: `tests/unit/server/ws.test.ts` (forwards/omits `nodeToFrame`), `tests/unit/server/app.test.ts` (commit broadcast + `/step` advance), `tests/unit/server/mcp.test.ts` (`step()` advance + commit broadcast). Full suite: 444 passing, `tsc --noEmit` (server + client) and `npm run lint` clean. Verified live: clicking each mapped node in `tests/human_driven/showcase.js`'s Section 14 (`runNodeToFrameDemo`) jumps to its frame
154
+
155
+ ## 0.26.0 — 2026-07-09
156
+
157
+ **Milestone v0.26 — Architecture Consolidation: Unified Presentation Model (Sprints 39–48) complete.** Everything renderable now collapses to one atom (`Frame`) and one container (`Presentation`) on both server and client; `type:"step-frames"` no longer exists as a top-level content type anywhere in the codebase. This was the highest-risk slice of the architecture consolidation (`04_architecture.md` §9) and is gated as a prerequisite for any public release (`02` N4).
158
+
159
+ - **Sprint 48 — Full-suite regression & contract parity check (verification only):** ran the full unit suite (439/439 passing, up from a 365-test pre-migration baseline) and e2e suite (37/38, the one failure a pre-existing dev-server-startup race unrelated to this milestone, confirmed passing in isolation); typecheck and lint clean. Grepped the codebase for stale WS/MCP/snapshot-shape assertions and back-compat shims — none found in runtime code (the only matches were historical prose, the migration script's intentional old-format detection, and unrelated identifiers sharing a name)
160
+ - Found and fixed one real documentation gap: `README.md`'s `wait_click()`/`commit_step_frames()` sections still described the pre-Sprint-47 behavior (no `type:"superseded"`, `node_to_frame` requiring a fresh `render()` to re-enable) — updated to match the shipped contract, closing the milestone's `README.md` Definition-of-Done item
161
+ - Summary of the full milestone: `Presentation`/`Frame` data model (Sprint 39) → server + client reducer rewrites (40–41) → unified WebSocket contract (42) → unified on-disk snapshot schema + migration script (43) and its real run against the live `~/.agent-whiteboard/` directory (44) → MCP payload contract update removing `type:"step-frames"` (45) → return-channel Interaction primitive unification (46) → supersession + `node_to_frame` auto-restore (47) → this final regression check (48)
162
+
163
+ ## 0.25.9 — 2026-07-09
164
+
165
+ - **Sprint 47 — Return channel: supersession & auto-restore (U7, OQ11/OQ12):** `wait_click()` gains `type: "superseded"`, distinct from a genuine `type: "timeout"`, when a new `wait_click()` or an arming `wait_done()` cancels a pending one — `server/interaction.ts`'s `createSingleFlightInteraction()` now takes separate `timeoutEvent`/`supersededEvent` values plus a `supersede()` method; `waitForDone()` calls `clickInteraction.supersede()` on arm, so arming Done now takes over the return channel from a pending click instead of leaving it to time out
166
+ - **`node_to_frame` now auto-restores after `wait_click()` resolves** (previously required the agent to build and commit a fresh sequence): a pure client-side reducer change — `canvasStore.ts`'s `set_node_actions` handler sets `nodeToFrameEnabled: state.nodeToFrame !== undefined` on `enabled:false` instead of hardcoding `false`, since the map itself was never actually cleared by arming/disarming a click listener
167
+ - New behavior, not a refactor — `docs/03_requirements.md` (wait_click response shape, U4e) and `docs/04_architecture.md` (§9.2 U7, wait_click data flow) updated to describe the shipped contract
168
+ - Verified by the full unit suite (439 tests, up from 431 — new coverage in `interaction.test.ts`, `app.test.ts`, `mcp.test.ts`, `canvasStore.test.ts` for both supersession and auto-restore) and typecheck (`tsc` + lint, 0 errors); e2e: 37/38, the one failure is the same pre-existing dev-server-startup race documented in prior sprints, confirmed passing in isolation
169
+
170
+ ## 0.25.8 — 2026-07-09
171
+
172
+ - **Sprint 46 — Return channel: Interaction primitive unification (U7, D4):** `server/events.ts`'s bespoke EventEmitter bus is replaced by `server/interaction.ts`, which exposes two generic arm/await/resolve factories — `createBroadcastInteraction<E>()` (every pending `await()` resolves independently, one `resolve()` wakes all — the shape `wait_done()` needs) and `createSingleFlightInteraction<E>(cancelEvent)` (a new `await()` cancels the pending one with `cancelEvent` — the shape `wait_click()` needs)
173
+ - `signalDone`/`waitForDone`/`getDoneArmed`/`setBroadcastFn` and `signalClick`/`waitForClick`/`resetClick`/`ClickEvent` are now thin configurations built on those two factories, with identical exported signatures — `app.ts`, `mcp.ts`, and `ws.ts` needed only an import-path change, no logic changes
174
+ - `node_to_frame` (U4e) needed no code change: it was already the "local resolver" variant D4 describes — the browser calls `POST /seek` directly from `Mermaid.svelte`'s click listener, never round-tripping through the server's return channel — now documented as such in `04_architecture.md` §9.2 (U7) rather than force-fit into the new module
175
+ - Behavior-preserving refactor: `tests/unit/server/events.test.ts` renamed to `interaction.test.ts`, all prior behavioral assertions kept verbatim, plus new tests exercising the two factories directly. Full suite: 431 tests passing (up from 423), `tsc --noEmit` and `npm run lint` clean
176
+
177
+ ## 0.25.7 — 2026-07-09
178
+
179
+ - **Sprint 45 — MCP payload contract update (U0/U2):** `type: "step-frames"` no longer exists as a top-level content type — `render()` is single-frame only (its `type` enum drops to the five `FRAME_TYPES`, `options.node_to_frame` removed). `init_step_frames`/`append_frame`/`commit_step_frames` is now the sole way to create a multi-frame sequence; `node_to_frame` moves to an optional `commit_step_frames(id, node_to_frame?)` parameter, its only entry point now (U4e). No back-compat shim, per `02` N4 — pre-release, zero external consumers
180
+ - **`slideshow()` slides are single-frame only:** the step-frames-slide-expands-into-per-frame-timer-ticks behavior is removed along with the top-level type it depended on; `slideshow.ts`'s `Tick`/`SlideTick`/`FrameTick`/`expandSlides`/`broadcastTick` machinery collapses to one `broadcastSlide()` call per slide
181
+ - **`validate.ts`:** `validatePayload()` and `KNOWN_TYPES` deleted — they had degenerated to a step-frames-envelope-only wrapper around `validateFrame()`, which every content path (`render()`, each `slideshow()` slide, `append_frame()`) now calls directly
182
+ - **`persist.ts`:** `PersistableContent`'s `type: CanvasType | "step-frames"` + `payload: string` shape replaced with `frames: Frame[]` + optional `rawPayload` — callers already hold resolved `Frame[]` internally, so the `toFrames()` envelope-unwrapping step it used to perform is gone
183
+ - Server unit tests rewritten against the new contract (423 tests, down from 444 — consolidates what used to be duplicate one-shot-vs-incremental step-frames test pairs into single incremental-protocol-only tests) plus `tests/e2e/canvas.spec.ts` updated to build step-frames sequences via the incremental REST protocol instead of one-shot `render(type="step-frames")`. `README.md`, `03_requirements.md`, and `04_architecture.md` updated to describe the shipped contract; `tests/human_driven/showcase.js` rewritten (Sections 1, 7, 8, 14) and verified end-to-end against a live `npm run dev:test` server
184
+
185
+ ## 0.25.6 — 2026-07-08
186
+
187
+ - **Sprint 44 — Snapshot migration real run (U2):** ran `server/migrate-snapshots.ts` against the real `~/.agent-whiteboard/` directory (161 snapshot files across ~10 workspaces). Backed up first to `~/.agent-whiteboard.backup-20260708-205116/`; verified a real run against a throwaway copy byte-for-byte against the backup (id/timestamp/workspace/title/nodeToFrame/frames/rawPayload all preserved, 0 mismatches, 0 pre-migration files missing an `id`) before running for real. Post-run: re-diffed against the backup (0 mismatches), confirmed idempotency (re-run reports 0 migrated / 161 already-migrated / 0 errors), and smoke-tested `GET /snapshots` / `POST /snapshots/load` / `GET /export` end-to-end against real migrated data with a live server. No code changes — operational only
188
+
189
+ ## 0.25.5 — 2026-07-08
190
+
191
+ - **Sprint 43 — Snapshot schema + migration script (U2):** on-disk snapshot files move from the old top-level `type`/`payload`/`options` triple to a `Presentation`-shaped `{ id, timestamp, workspace, cursor, frames[], title?, nodeToFrame?, rawPayload? }` schema (`SnapshotFile` in `server/snapshot.ts`). A one-shot render is a single-element `frames` array; a step-frames sequence is the full array with each frame's already-resolved effective type. `rawPayload` — the verbatim step-frames envelope, kept only when `frames.length > 1` — lets `export(id)` return byte-identical content instead of a reconstruction; a committed 1-frame sequence collapses into a plain record with no `rawPayload`, mirroring the WS contract's Sprint 42 policy
192
+ - **Full cutover (user decision, not deferred):** every read path — `snapshot-reader.ts` (`listSnapshots`/`findSnapshotById`/`findSnapshotByIdInWorkspace`, plus a new `badgeType()` helper deriving the list's display type since there's no top-level `type` field anymore), `POST /snapshots/load`, `POST /export-html`, and `export-html.ts` — rewritten onto the new schema in this same sprint, so the repo stays working the moment real data is migrated (Sprint 44)
193
+ - **`server/migrate-snapshots.ts`:** one-time, idempotent migration script — `migrateSnapshotFile()` is a pure old-to-new transform (backfills a missing `id`, detects already-migrated files via presence of a `frames` array), `migrateDirectory()` is the filesystem driver with dry-run support (required before the real run, per N5 in `02`), plus a CLI entry point
194
+ - **Fixed a pre-existing client crash found during this sprint's manual test gate** (unrelated to the schema work — zero client files touched by Sprint 42 or 43): `App.svelte`'s `rendererProps` computed eagerly off `currentComponentType` alone; a WS `clear()` racing with an already-cached renderer type's first-ever async load could leave `currentComponentType` pointing at a type with no content, crashing on `registry.ts`'s non-null `presentation` assertions. Fixed by reusing the template's `currentComponentType === rendererKey` guard in the reactive statement itself
195
+ - Verified by the full unit suite (444 tests, up from 415 — new `migrate-snapshots.test.ts` plus every fixture touching the on-disk schema rewritten across `snapshot`/`persist`/`snapshot-reader`/`app`/`mcp`/`export-html` tests) and typecheck (`tsc` + `svelte-check`, 0 errors); e2e: 37/38, the one failure is the same pre-existing dev-server race confirmed unrelated in the Sprint 42 changelog entry
196
+
197
+ ## 0.25.4 — 2026-07-08
198
+
199
+ - **Sprint 42 — WebSocket contract update (U3):** the v0.23 broadcast projector now always carries `id`, `cursor`, and `total` on every content `replace` message, replacing the old `stepFrames` boolean plus optional `currentFrame`/`totalFrames` pair. A one-shot render is `cursor: 0, total: 1`; a step-frames frame is `cursor: N, total: M`. `ReplaceBroadcast` in `server/ws.ts` becomes a discriminated union (content vs. the `init_step_frames` placeholder) so the type system requires every real call site to supply `id`/`cursor`/`total`; `broadcastStepFrames()`'s signature reorders to `(frames, frameType, cursor, id, title?)` since `id` is no longer optional
200
+ - **Every server call site updated:** `render-core.ts`, `slideshow.ts`, `app.ts`, and `mcp.ts` now pass `cursor`/`total` on every broadcast; a fresh id is synthesized via `generateSnapshotId()` wherever the in-memory `Presentation` or an on-disk snapshot might lack one (legacy pre-v0.11 snapshots loaded via `POST /snapshots/load`), rather than omitting the field as before
201
+ - **Client step-bar visibility now derives from `total > 1`** instead of the removed `stepFrames` flag: a committed 1-frame step-frames sequence is now treated as static (no navigation UI needed, matching a one-shot render) — a deliberate behavior decision, not an oversight
202
+ - Verified by the full unit suite (415 tests, up from 414 — `ws.test.ts`/`canvasStore.test.ts`/`app.test.ts`/`mcp.test.ts`/`slideshow.test.ts` rewritten against the new shape) and typecheck (`tsc` + `svelte-check`, 0 errors); e2e: 36/38, the same 2 pre-existing dev-server-race failures confirmed present at the same rate on unmodified pre-Sprint-42 code via a stash-and-compare run
203
+
204
+ ## 0.25.3 — 2026-07-08
205
+
206
+ - **Sprint 41 — Client reducer rewrite (U3):** `canvasStore.ts` mirrors `session.ts`'s server-side shape: `{ presentation: Presentation | null; driver }` instead of a `type`-tagged `CanvasState` union. New `client/src/presentation.ts` defines `Frame`/`Presentation`, matching `server/presentation.ts`
207
+ - **`frames` holds just the current frame; `cursor` stays 0 until Sprint 42** changes the WS payload to carry the full sequence — the client only ever receives one frame per broadcast today. `currentFrame`/`totalFrames` stay as separate display-only metadata for the step-bar
208
+ - **`driver` is `"manual"` whenever the current content is part of a step-frames sequence** (`cmd.stepFrames`), mirroring `session.ts`'s driver semantics — the step-bar now shows on `driver === "manual"` instead of a `type !== "step-frames-placeholder" && stepFrames` check
209
+ - **`App.svelte` and `registry.ts` updated** to read `presentation`/`driver`/`placeholder` instead of pattern-matching `canvas.type`
210
+ - **Pure internal refactor, no WS contract change** (that's Sprint 42): verified by the full unit suite (414 tests, `canvasStore.test.ts`/`registry.test.ts` rewritten against the new shape) and typecheck (`tsc` + `svelte-check`, 0 errors); e2e failures across repeated runs are a pre-existing dev-server race, confirmed present at the same rate on unmodified pre-Sprint-41 code
211
+
212
+ ## 0.25.2 — 2026-07-08
213
+
214
+ - **Sprint 40 — Server reducer rewrite (U3):** `session.ts`'s `CanvasState` 3-way union (`empty` | single-type | `step-frames`) is replaced by one `{ presentation: Presentation | null; driver }` model. `driver` is `"static"` for a one-frame render, `"manual"` whenever a step-frames sequence is loaded (one-shot render, `append_frame`, `commit_step_frames`, or a slideshow tick/finalize) — exactly the set of cases the old `type === "step-frames"` branch covered, so `step()`/`seek()` behavior (including "Prev/Next keeps working during a slideshow", F7) is unchanged
215
+ - **`setStepFrames()` resolves each frame's effective type once, at intake** (`frame.type ?? frameType`), instead of every reader doing that lookup inline
216
+ - **New `isStepSequence()` type guard** replaces the old `canvas.type === "step-frames"` check at every read site (`app.ts`'s `/step`/`/seek`, `mcp.ts`'s `step`/`seek` tools, `slideshow.ts`'s tick broadcast and finalize)
217
+ - **Pure internal refactor, no contract change:** no WS/MCP/snapshot schema change (that's Sprint 42/45); verified by the full unit suite (413 tests — `session.test.ts` rewritten against the new shape) and the e2e suite (37/38 passing; the one failure is the pre-existing webServer-startup race noted in the 0.24.0 changelog, unrelated since this change only touches `server/*.ts`)
218
+
219
+ ## 0.25.1 — 2026-07-08
220
+
221
+ - **v0.26 (Architecture Consolidation — Unified Presentation Model) split into 10 sprints (39–48):** the milestone's original single Sprint 39 bundled 7 tasks together; split along real seams (server/client reducer, migration build/run, return-channel refactor/new-behavior) and folded contract-test rewrites into each sprint that introduces the change instead of one final rewrite sprint — see `docs/05_dev-plan.md` and `docs/milestones/Milestone_v0.26.md`
222
+ - **Sprint 39 — Presentation/Frame data model (U2):** introduces `Presentation`/`Frame` as the target content model (`docs/04_architecture.md` §9.1) in new `server/presentation.ts`, and extracts `validateFrame()` as the single atomic-frame validator. `validatePayload()` is now a thin step-frames-envelope dispatcher that loops `validateFrame()` per frame; `step-frames-builder.ts`'s `appendFrame()` calls `validateFrame()` directly instead of going through the envelope path
223
+ - **Removes a real duplication:** `app.ts` had its own locally-redeclared `KNOWN_TYPES`/`FRAME_TYPES` literals, independent copies of `validate.ts`'s lists — both now import the canonical exports
224
+ - **Pure internal refactor, no contract change:** no MCP tool signature, WebSocket message shape, or snapshot schema changed; verified by the full test suite (409 tests, up from 392 — 17 new tests directly covering `validateFrame()`/`validatePayload()`) passing unchanged
225
+
226
+ ## 0.25.0 — 2026-07-07
227
+
228
+ - **Architecture Consolidation — Persistence Policy & Finalize Dedup milestone (Sprint 38):** slice C of the architecture consolidation promoted from `desing-analysis/` (FR22) — the persistence/finalize-side counterpart to v0.23's unified broadcast projector
229
+ - **New persist-trigger vocabulary, enforced by a registry:** `server/persist.ts` defines `PersistTrigger` (`immediate | on-finalize | transient | never`) and a `COMMAND_PERSIST_TRIGGERS` map from command name to trigger; `getPersistTrigger()` throws for any unregistered command instead of silently doing nothing. This is the direct structural fix for the FR20/B15 class of bug — `slideshow()` used to have no persistence decision recorded anywhere and simply never called `saveSnapshot()`
230
+ - **One shared finalize/persist implementation (`persistContent()`) replaces three independent copies:** `render-core.ts`'s `commitRenderResult()` and `commitStepFramesResult()`, and `slideshow.ts`'s `finalizeSlideshow()`, each used to re-implement the same step-frames JSON assembly and "a write failure must never block" backstop separately. All three now call `persistContent()` (`assembleStepFramesPayload()` covers the JSON assembly), each declaring its own trigger: `render`/one-shot step-frames = `immediate`; `append_frame` = `transient`; `commit_step_frames`/slideshow-end = `on-finalize`; `step`/`seek`/`clear`/history-load = `never` (these never called `saveSnapshot()` before either — now documented in the registry rather than left implicit)
231
+ - **Pure internal refactor, no contract change:** F7 (slideshow finalize-on-end), F10 (render snapshot persistence), and F15 (incremental step-frames finalization) behavior is unchanged — verified by the full pre-existing test suite (`slideshow.test.ts`, `snapshot.test.ts`, `app.test.ts`, `mcp.test.ts`) passing unmodified, plus 15 new tests in `persist.test.ts` covering registry enforcement and per-trigger write behavior (392 total, up from 377)
232
+
233
+ ## 0.24.0 — 2026-07-07
234
+
235
+ - **Architecture Consolidation — Client Renderer Registry milestone (Sprint 37):** slice B of the architecture consolidation promoted from `desing-analysis/` (FR22) — a pure client-side refactor, isolated and low-risk
236
+ - **`App.svelte`'s hardcoded `{#if canvas.type === "mermaid"} … {:else if …}` dispatch chain replaced by a `type → component` registry:** `client/src/renderers/registry.ts` maps each canvas type (`mermaid`, `svg`, `html`, `katex`, `vega-lite`, `step-frames-placeholder`) to its component plus a props-mapping function; `App.svelte` resolves and renders via `<svelte:component>` instead of a growing `{:else if}` ladder. Adding a future renderer type is now one registry entry — no `App.svelte` changes needed (U6/D3 in `04_architecture.md` §9)
237
+ - **v0.21 lazy-loading of the heavy rendering libraries preserved:** the registry resolves each wrapper component (`Mermaid.svelte`, `Html.svelte`, `Katex.svelte`, `VegaLite.svelte`, `StepFramesPlaceholder.svelte`) from a static import rather than a dynamic `import()` of the wrapper file — the wrappers are tiny (~13kB combined) next to the actual v0.21 win (mermaid.js/katex/vega-embed, hundreds of kB each), which lives inside each wrapper, untouched, and stays genuinely lazy (confirmed via `npm run build`: separate `mermaid.core-*`/`katex-*`/`embed-*` chunks, fetched only on first use of that type). Dynamically importing the wrappers too was tried and reverted during implementation — it shrank the main bundle just enough to shift page-load timing and made a pre-existing, razor-thin dev-server race (a REST-triggered `render()` arriving before the fresh page's own `/stream` WebSocket handshake completes) fail far more often in the Playwright e2e suite
238
+ - No WebSocket message shape, MCP contract, or snapshot schema change; verified against the full unit suite (377 tests, up from 371 — 6 new tests directly covering the registry) and the 38-test e2e suite, both passing unchanged and stable across repeated runs
239
+
240
+ ## 0.23.0 — 2026-07-07
241
+
242
+ - **Architecture Consolidation — Unified Projector milestone (Sprint 36):** slice A of the architecture consolidation promoted from `desing-analysis/` (FR22) — the analysis's own "80/20": the single highest-value, lowest-risk structural win, landed over today's `CanvasState` with no schema change
243
+ - **One shared broadcast builder replaces 13 hand-built sites:** every server→browser `{ action: "replace", ... }` message — from `render()`, `step()`, `seek()`, history-load (`POST /snapshots/load`), and slideshow ticks/finalization — previously had its own hand-assembled construction spread across `app.ts` (×4), `mcp.ts` (×2), `render-core.ts` (×3), `slideshow.ts` (×3), and `ws.ts` (×1). All 13 now route through one new function, `broadcastReplace()` in `server/ws.ts` (plus `broadcastStepFrames()`, reimplemented as a thin convenience wrapper over it). This is the structural end of the B15/C2b/C2d drift class: a later broadcast producer forgetting a field (`id`, `viewport`, `nodeToFrame`, the step-frames cursor) that another producer already threads through is no longer possible, because there is only one producer
244
+ - **Pure internal refactor, no contract change:** the content model, snapshot schema, and MCP payload contract are untouched — only *how* the existing broadcast shape gets constructed. No WebSocket message shape or API contract change; verified against the full unit + e2e suites
245
+ - 371 unit tests (up from 365 — 6 new tests directly covering `broadcastReplace()`'s id/cursor/viewport/nodeToFrame inclusion rules) / 38 e2e tests, all pass unchanged
246
+ - `04_architecture.md` §2/§3 updated to describe the single projector in place of the per-site broadcast descriptions; §9's target-architecture tables mark U5/Slice A as shipped
247
+
248
+ ## 0.22.0 — 2026-07-07
249
+
250
+ - **Showcase Coverage, Step-Frames Fit Fix & Slideshow Persistence milestone (Sprint 35):** opened from a feature request (does `tests/human_driven/showcase.js` demonstrate every shipped feature, excluding delete/export?) plus a step-frames fit bug report; three further items (B16, FR20, B17) surfaced during verification and were folded in rather than deferred, since each blocked verifying the item before it
251
+ - **Fix — slideshow-driven renders never auto-fit (bug B15):** `server/slideshow.ts`'s `broadcastSlide()`/`broadcastTick()` never included an `id` in their WebSocket broadcasts, so the browser's `isNewSnapshot()` check (F19) was always false for `POST /slideshow`-driven content and auto-fit never triggered. Fixed by generating and threading a snapshot `id` through every slideshow broadcast — fresh per plain slide, one id shared across a step-frames sequence's frame ticks (continuation semantics, matching `/step`/`/seek`). Necessary but, as B17 revealed, not sufficient on its own
252
+ - **Fix — the real root cause: unsized Mermaid SVGs (bug B17):** even after B15, diagrams still rendered small/uncentered *live* (History-reloaded content was always correct). `fitToView()`'s computed scale was mathematically correct against the `viewBox`, but `getBoundingClientRect()` showed the SVG actually rendering at ~300×150 — the CSS spec's default replaced-element size — because Mermaid emits `width="100%"` with no explicit height and `Mermaid.svelte` deliberately leaves the container unsized. Headless Chromium (used for all automated reproduction) resolves the percentage differently and never hit this fallback, which is why every automated repro looked fine and only a real browser reproduced it. Fixed by explicitly pinning the SVG's `width`/`height` attributes to its `viewBox` dimensions immediately after insertion; confirmed fixed live by the user
253
+ - **Feature — slideshow finalize-on-end persistence + required `workspace` (FR20):** `POST /slideshow` never called `saveSnapshot()` (since Sprint 9), so nothing shown via a slideshow ever appeared in History. Now models "transient until finalized" mirroring `init_step_frames`/`append_frame`/`commit_step_frames` (F15): individual ticks stay transient, and exactly one snapshot is written when the session ends (natural completion, explicit stop, or supersession by a new `render()`/`slideshow()`), capturing whatever was last on screen. Adds a now-required `workspace` parameter to `slideshow()`/`POST /slideshow` (explicit-required, no `lastWorkspace` fallback, matching `render()`'s F14 pattern). `clear()` deliberately does not finalize (`cancelSlideshow({ persist: false })`), preserving F10
254
+ - **Feature — showcase feature-coverage audit (FR19):** audited showcase Sections 1–12 against `03_requirements.md`'s full feature list (excluding delete/export per instruction); found two shipped MCP features with no demo — the incremental step-frames protocol (F15) and `node_to_frame` autonomous navigation (U4e). Added as Section 13 and Section 14 to `tests/human_driven/showcase.js` with new `-c`/`--incremental` and `-n`/`--nodetoframe` flags (folded into `-a`/`--all`)
255
+ - **Fix — `node_to_frame` clicks 404 in dev mode (bug B16):** `client/vite.config.ts`'s dev proxy never listed `/seek`, the one endpoint the browser calls directly for `node_to_frame`. Added the proxy entry; production single-origin builds were never affected
256
+ - **Not implemented — re-fit per frame for step-frames (FR21):** fixing B17 exposed the residual risk in C3 (`02`) that a step-frames sequence's single shared fit (computed at frame 0) can overflow/under-fill later frames of very different size. Intentionally intake-only per explicit user instruction — tracked in `01`/`02`/`03`, no milestone assigned
257
+ - 365 unit tests (up from 356), typecheck clean (server `tsc` + client `svelte-check`); slideshow auto-fit and `node_to_frame` dev-mode behavior confirmed live in a real browser
258
+
259
+ ## 0.21.0 — 2026-07-05
260
+
261
+ - **Design Debt — Core Consolidation milestone:** the second of two milestones promoting the Design Debt Log (`01_input-ideas.md`) into scheduled work — the behavior-risk refactor pass, sequenced after v0.20's safety net (linter + blanket test coverage)
262
+ - **`App.svelte` decomposed into stores/reducers:** the 449-line god component is split into five focused modules under `client/src/stores/`: `canvasStore` (canvas content + clickable/nodeActions/nodeToFrame state), `doneStore` (Done-button armed/sent/error lifecycle), `modalStore` (delete/export modal orchestration), `stepNav` (step-frame prev/next), and `wsRouter` (WebSocket connection lifecycle + command fan-out to the other stores). `App.svelte` is now a thin composition layer; no template, CSS, or behavior changes
263
+ - **Mermaid/KaTeX/Vega-Embed switched to per-canvas-type dynamic `import()`:** each renderer now lazy-loads its heavy rendering library on first use instead of eager static import, cached after the first call. Main entry bundle drops from ~1.7MB to ~78KB; each library ships in its own chunk fetched only when that canvas type is first rendered. A one-time loading delay on first use is an accepted trade-off — no loading spinner added. Added render-token guards to KaTeX (matching the existing Mermaid/VegaLite B8 pattern) so a superseded render can't land after a slower one during the one-time load
264
+ - **Shared `server/render-core.ts` module:** `server/app.ts` (REST) and `server/mcp.ts` (MCP tools) each reimplemented the same render / step-frames init/append/commit / workspace-validation logic with identical broadcast, snapshot, and error-message behavior. That logic now lives in one shared module (`validateWorkspaceInput`, `commitRenderResult`, `initStepFramesResult`, `appendFrameAndBroadcast`, `commitStepFramesResult`) so both transports route through one implementation and can't drift. Also removed `mcp.ts`'s `render` handler's manual per-type mermaid/vega-lite validation, which duplicated `validate.ts`'s `validatePayload()` verbatim — it now calls `validatePayload` directly, matching `app.ts`'s existing approach
265
+ - No external behavior, API contract, or WebSocket message shape changes from any of the three refactors — verified against the full existing suite
266
+ - 356 unit tests (up from 331; 5 new client test files for the extracted stores) / 38 e2e tests, all pass unchanged
267
+
268
+ ## 0.20.0 — 2026-07-05
269
+
270
+ - **Design Debt — Safety Net milestone:** the first of two milestones promoting the Design Debt Log (`01_input-ideas.md`) into scheduled work, split by regression risk — this one is additive/no-behavior-change, and safety-nets the behavior-risk refactors planned for v0.21
271
+ - **Linting:** ESLint (`eslint-plugin-svelte` + `@typescript-eslint`) added for both `client/` and `server/`, runnable via `npm run lint` / `npm run lint:fix`. Scoped conservatively — catches real bugs, not full stylistic conformance; not wired into the build gate yet
272
+ - **Hygiene/a11y fixes:** placeholder/zoom-hint text contrast brought up to WCAG AA (`#aaa`/`#bbb` → `#666`); `aria-live`/`role="alert"` added to the disconnect banner and the Done button's state text; the Mermaid popup menu's `{#each}` block is now keyed; a redundant `svelte-ignore` comment removed; `getMermaidBundle()`/`getKatexCss()` are now memoized instead of re-reading from disk on every export; several previously-silent `catch {}` blocks now log (closing a gap where F11 promised a stderr warning that never actually fired, plus `viewport-cache.ts`'s cache-corruption path and `HistoryPanel`'s failed-load path). The debt log's "redundant try/catch around `saveSnapshot()`" claim was investigated and found incorrect — it's a deliberate F10 caller-level backstop proven by an existing test, kept as-is and now documented with a comment
273
+ - **Content-Security-Policy:** a CSP header is now sent on every `server/app.ts` response, and the self-contained HTML export gets its own `<meta http-equiv>` CSP tag (the header has no effect once the file is opened locally, so this is where it actually matters); Mermaid's `securityLevel` is now set explicitly (`"strict"`, matching its existing default) at all three `mermaid.initialize()` call sites
274
+ - **`@types/katex` removed:** the debt log assumed a `0.16.8` → `0.17.x` bump; no such release exists on npm. `katex@0.17.0` now ships its own native types, making the separate DefinitelyTyped package obsolete — removed instead of bumped
275
+ - **Blanket unit test coverage:** 5 new server test files (`session.ts`, `events.ts`, `ws.ts`, `slideshow.ts`, `channel.ts`); `mcp.ts` coverage deepened from 15 to 39 tests; all 7 previously-unit-tested-only-via-e2e Svelte components now have unit tests (`App`, `HistoryPanel`, `DeleteExportModal`, `Mermaid`, `Html`, `Katex`, `VegaLite`), including a real DOMPurify-sanitization test and the U7i single/multi-workspace modal-step behavior. New client component testing infra: `@testing-library/svelte` + `happy-dom`
276
+ - 331 unit tests (up from 260) / 38 e2e tests, all pass
277
+
278
+ ## 0.19.0 — 2026-07-04
279
+
280
+ - **Mermaid zoom/pan: fit-to-view on first display:** every new `render()`/`commit_step_frames()` result (or a `POST /snapshots/load` history reload) now auto-fits the diagram — scaled to contain, centered — on first display, replacing the old behavior of resetting to a raw 1:1 transform on every render
281
+ - **Zoom/pan preserved across step-frames navigation:** `step()`/`seek()` within the same step-frames sequence no longer resets the live viewport — the whole sequence shares one continuously-adjustable view
282
+ - **Manual zoom/pan is persisted per snapshot:** the browser debounces zoom/pan changes (~800ms) and reports them to a new `POST /viewport` endpoint, keyed by the snapshot's `id`; the server stores them in a new global `viewport-cache.json` file (`server/viewport-cache.ts`) — separate from the immutable snapshot JSON files — and restores the saved viewport instead of auto-fitting whenever that exact snapshot is redisplayed
283
+ - **Viewport-cache cleanup on delete:** `POST /snapshots/delete-files` and `POST /snapshots/delete-workspace` now also remove the corresponding viewport-cache entry/entries, so deleted snapshots don't leave orphaned rows behind
284
+ - **Fix: `/viewport` was missing from the Vite dev-server proxy list** (`client/vite.config.ts`) — without it, the browser's viewport reports silently never reached the real server during local dev; caught by the new e2e persistence test
285
+ - Mermaid-only; no MCP tool — this is a pure browser⇄server concern, the agent is unaware of it
286
+ - 16 new unit tests (`viewport-cache.test.ts` plus additions to `app.test.ts`/`snapshot.test.ts`/`mcp.test.ts`) and 3 new e2e tests (auto-fit, step/seek continuity, persistence-through-reload) — 260 unit tests / 38 e2e tests all pass
287
+
288
+ ## 0.18.0 — 2026-07-04
289
+
290
+ - **Stability & correctness fixes (B6–B14, from a Node.js/TS + frontend code review pass):**
291
+ - **B6 — workspace-validation gap:** `POST /snapshots/delete-workspace` accepted `{"workspace": "."}`, which resolved to the snapshots root and recursively deleted every workspace's history in one call. All workspace-name checks across `app.ts` (`GET /snapshots`, `POST /snapshots/load`, `POST /export-html`, both delete endpoints) now route through the shared `isValidWorkspaceName()`; the delete-workspace endpoint additionally asserts the resolved path stays strictly inside the snapshots root before `rmSync`
292
+ - **B7 — snapshot filename collisions:** two `render()`/`commit_step_frames()` calls in the same wall-clock second silently overwrote each other's snapshot file. Filenames now include the snapshot's own `id` UUID
293
+ - **B8 — stale async renders:** `Mermaid.svelte` and `VegaLite.svelte` could display a stale diagram/chart if an older in-flight render resolved after a newer one. Both now discard a superseded render's result via a generation token
294
+ - **B9 — unhandled Done-button fetch rejection:** a failed `POST /user-done` left the Done button's state machine stuck with no feedback. `handleDone()` now catches the failure, shows a "Failed ✗" state, and stays retryable
295
+ - **B10 — client TypeScript never type-checked:** `npm run build` only ran `tsc` against `server/`. Added `svelte-check` + a `typecheck` script, chained into `build`; fixed the three real type errors it surfaced (an invalid `res.json<T>()` call, a non-discriminating `RenderCommand` union, and an under-narrowed step-bar visibility check)
296
+ - **B11 — unvalidated WebSocket messages:** an unrecognized `type` in a WS message silently rendered nothing. `ws.ts` now validates message shape before dispatch and logs a diagnostic for anything unrecognized instead of dropping it silently
297
+ - **B12 — dialogs not keyboard-accessible:** `DeleteExportModal` and `HistoryPanel` had no `aria-modal`, Escape handling, or focus trap. Both now use a shared `trapFocus` Svelte action
298
+ - **B13 — inconsistent snapshot-fetch error handling:** `App.svelte`'s delete/export modal silently fell back to an empty workspace list on a failed `GET /snapshots/all`, while `HistoryPanel` already surfaced the error. Extracted a shared `fetchAllSnapshots()` helper; both call sites now surface failures visibly
299
+ - **B14 — concurrent export-html corruption:** `generateExportHtml()` patches Node's `global.*` DOM state for the call's duration with no lock; overlapping calls (from `POST /export-html` and the `export_html` MCP tool) could leave global state dangling on an already-closed Window. Calls are now serialized via a promise queue
300
+ - `01`–`04` design docs updated to reflect resolved status for all nine bugs
301
+ - 229 unit tests (7 test files, including 3 new: `snapshot.test.ts`, `export-html.test.ts`, and the project's first client-side test `ws.test.ts`) / 35 e2e tests (5 new) all pass
302
+
303
+ ## 0.17.1 — 2026-07-03
304
+
305
+ - **Fix: Done button confirmation never rendered (client only, no server change):** `set_done_armed: false` arrives over the WebSocket almost immediately after a click — the server unarms as part of resolving `wait_done()` — and was hiding the whole Done button and force-resetting `doneSent` before the "Sent ✓" text ever had a chance to render, confirmed via live DOM instrumentation in a real browser. `client/src/App.svelte` now shows the button on `doneArmed || doneSent`, letting `doneSent`'s own 2-second timer own its lifecycle instead of being cut short by the unarm broadcast
306
+ - **Fix: Done button e2e test was stale since v0.12 (Sprint 25):** the test predated conditional Done-button visibility and asserted the pre-v0.12 always-visible-button behavior, so it had been silently failing every `npm run test:e2e` run since; split into two tests — hidden-until-`wait_done()`-armed, and click-shows-Sent-then-disappears (no longer "reverts to visible Done")
307
+ - 220 unit tests / 30 e2e tests all pass (previously 28/29 e2e — this release fixes the last failing one)
308
+
309
+ ## 0.17.0 — 2026-07-03
310
+
311
+ - **Step-frames per-frame validation parity (bug B5):** `render(type="step-frames", ...)` now validates every frame's payload against its effective type before accepting the sequence — previously it only checked payload shape, so a malformed mermaid or vega-lite frame was silently accepted and only failed when the user stepped or seeked to it. `append_frame()` already validated per frame; both creation paths now give the same guarantee, enforced by a single shared code path in `validatePayload()` (`server/validate.ts`) that also covers `POST /slideshow` and `POST /snapshots/load` for free
312
+ - **Per-frame `type` override:** `StepFrame` gains an optional `type` field; a step-frames sequence can now mix content types (e.g. a mermaid frame followed by a katex frame) via `type` on individual frames in the one-shot payload, or as a new optional 4th argument to `append_frame(id, payload, label?, type?)` / `POST /step-frames/:id/frame` body / the MCP `append_frame` tool
313
+ - **Broadcasts use each frame's own type:** `ws.ts`, `app.ts` (render/step/seek/snapshots-load), `mcp.ts` (render/step/seek), and `slideshow.ts` (tick/slide expansion) all send `frame.type ?? frameType` instead of the sequence-level type, so navigating or auto-advancing through a mixed-type sequence renders each frame with its correct renderer
314
+ - **`mcp.ts`'s `render` tool step-frames branch refactored** to delegate to the shared `validatePayload()` instead of duplicating shape-check logic inline
315
+ - 10 new unit tests across `app.test.ts`, `mcp.test.ts`, and `step-frames-builder.test.ts` (220 total), plus one new Playwright e2e test proving a mixed mermaid+katex sequence renders correctly in a live browser (28/29 e2e; 1 pre-existing unrelated Done-button flake)
316
+
317
+ ## 0.16.0 — 2026-07-03
318
+
319
+ - **Delete/export controls moved to the right-side controls panel:** the recycle-bin and export icons are removed from the history panel header; two new icon buttons (delete, export) appear in the always-visible right-side controls panel, grouped with a divider between the history-toggle and Done buttons
320
+ - **New 2-step delete/export modal (`client/src/DeleteExportModal.svelte`):** clicking either icon opens a modal — step 1 lists workspaces to choose from (name + snapshot count), auto-skipped straight to step 2 when only one workspace has snapshots; step 2 shows a single "Delete/Export entire workspace (N snapshots)" action plus a checkbox list of that workspace's snapshots, with a footer "N selected" bar for acting on a subset
321
+ - **Confirmation for destructive actions:** "Delete entire workspace" and "Delete selected" require a second confirming click (button relabels to "Click again to confirm" for ~3s) before executing, replacing the old `window.confirm()`; export actions execute immediately since they're non-destructive
322
+ - **History panel simplified (`client/src/HistoryPanel.svelte`):** removed the recycle-bin/export header icons, per-row checkboxes, select-bar, per-workspace action bar, and the always-visible per-row hover-delete button — the panel is now pure browse/load (header: title, lock/unlock, close); `fetchSnapshots()` is exported so `App.svelte` can refresh the list after a modal delete completes while the panel is open
323
+ - **Shared client types/utilities extracted:** `client/src/lib/snapshotTypes.ts` (`SnapshotEntry`, `WorkspaceGroup`) and `client/src/lib/download.ts` (`triggerDownload()`) — used by both the modal and the history panel
324
+ - No server/API changes — the modal calls the existing `POST /snapshots/delete-files`, `POST /snapshots/delete-workspace`, and `POST /export-html` endpoints
325
+ - Verified end-to-end with Playwright against a live dev server (real Chrome): controls-panel layout, both modal steps, whole-workspace/subset delete confirmation flow, immediate export with file download, single-workspace step-1 skip (via mocked data), and unaffected history browse/load, with no console errors
326
+
327
+ ## 0.15.0 — 2026-07-02
328
+
329
+ - **`list_snapshots(workspace)` MCP tool:** lists a workspace's snapshots (`id`, `timestamp`, `type`, optional `title`), newest-first, so an agent can discover what it can export without going through the browser HistoryPanel; wraps the same `listSnapshots()` used by `GET /snapshots`
330
+ - **`export_html(workspace, ids, output_path?)` MCP tool:** agent-facing equivalent of the HistoryPanel's "Export selected" — exports 1..N snapshots (addressed by `id`, not filename) to a single self-contained HTML file; the assembled HTML (which can be several MB once `mermaid.js` is embedded) is written to disk rather than returned inline, and the tool returns the absolute path. Defaults to `<WHITEBOARD_SNAPSHOTS_DIR>/<workspace>/exports/<name>-YYYYMMDD-HHmmss.html`; `output_path` (optional) writes anywhere, creating parent directories as needed
331
+ - **`findSnapshotByIdInWorkspace(workspace, id, dir)` in `server/snapshot-reader.ts`:** scoped variant of `findSnapshotById()` restricted to one workspace directory; returns the full parsed record (`type`, `payload`, `timestamp`, `options`) needed by the export pipeline, not just the payload
332
+ - **`GET /snapshots` extended:** accepts an optional `?workspace=` query param (validated with the same safe-name check as `POST /snapshots/load`) for explicit agent use; falls back to `lastWorkspace` when absent — the browser's existing call pattern is unchanged. Each entry gains an additive `id` field
333
+ - **`POST /export-html` extended:** items may now be `{ workspace, id }` in addition to the existing `{ workspace, filename }` form; both forms may appear in the same request. Unresolvable ids are skipped, same as unreadable files
334
+ - 25 new unit tests: `GET /snapshots?workspace=`, `POST /export-html` with `{ workspace, id }` items, `findSnapshotByIdInWorkspace()`/`listSnapshots()` id population against real files, and the two new MCP tool handlers (206 total)
335
+
336
+ ## 0.14.0 — 2026-07-02
337
+
338
+ - **Mermaid export fix (bug B4):** `POST /export-html` no longer pre-renders Mermaid diagrams server-side via `happy-dom` — `happy-dom` lacks real text-layout/font-metrics APIs, which caused invisible labels, a too-tight/incorrect viewBox, or thrown errors on diagrams with edge labels or certain node shapes
339
+ - **Client-side rendering:** Mermaid items (plain or step-frames frames with `frame_type: "mermaid"`) are now emitted as `<pre class="mermaid">` containers holding the raw, HTML-escaped source; when ≥1 Mermaid item is present, the full `mermaid.js` browser bundle is embedded inline as a `<script>` block (read from `mermaid/dist/mermaid.min.js`, no CDN reference — export stays fully offline-capable) alongside a bootstrap script that calls `mermaid.initialize({ startOnLoad: false })` and `mermaid.run()` on `DOMContentLoaded`
340
+ - **Removed:** `renderMermaid()` and `fixSvgViewBox()` (the happy-dom Mermaid render path and its viewBox-repair workaround); `happy-dom` is retained for the KaTeX/Vega-Lite/SVG/HTML export paths, which are unaffected
341
+ - 5 new server unit tests for the fixed Mermaid export path, including regressions for the three previously-failing scenarios (step-frames flowchart, seek demo, diagram with edge labels + cylinder node) — 181 total
342
+
343
+ ## 0.13.0 — 2026-06-30
344
+
345
+ - **HTML Export (`POST /export-html`):** exports one or more selected snapshots to a single fully self-contained HTML file (no external network requests); server-side rendering pipeline handles all snapshot types — KaTeX via `katex.renderToString`, Vega-Lite via `vl.compile` → `vega.View.toSVG`, SVG/HTML via DOMPurify + happy-dom, Mermaid via `mermaid.render` in a happy-dom Window with DOMPurify module patched for Node.js compatibility; items with render failures show an inline error message and do not block the rest of the export
346
+ - **Download filename:** single-workspace exports use the sanitised workspace name (`<name>-YYYYMMDD-HHmmss.html`); multi-workspace exports use `export-YYYYMMDD-HHmmss.html`
347
+ - **History panel — Export mode:** new export icon button in the panel header enters export mode (`selectionMode: 'delete' | 'export' | null`); select-bar shows "Export selected" (blue) when ≥1 item checked; each workspace accordion shows "Export workspace" to export all snapshots in that workspace without individual selection; clicking the opposite mode icon switches modes immediately without a cancel step
348
+ - **Clear workspace removed:** `POST /snapshots/clear-workspace` endpoint removed — it was functionally equivalent to workspace-delete from the user's perspective; "Clear all" button removed from HistoryPanel selection mode; three clear-workspace server tests removed
349
+ - **`happy-dom` dependency added** for server-side DOM host (DOMPurify + Mermaid SSR)
350
+ - 8 new server unit tests for `POST /export-html` (validation, path-traversal safety, malformed-snapshot skipping, single/multi-workspace filename format, HTML body/headers) — 176 total
351
+
352
+ ## 0.12.0 — 2026-06-29
353
+
354
+ - **Done button conditional visibility:** the Done button is now hidden by default and only appears while `wait_done()` is armed on the server; `server/events.ts` tracks `doneArmed` state and calls `broadcastDoneArmed()` on every change; `server/ws.ts` pushes `{ action: "set_done_armed", armed: <current> }` to every new WebSocket connection so a fresh browser tab initialises correctly; `client/src/App.svelte` shows/hides the button reactively
355
+ - **History panel — per-row delete:** each snapshot row shows a trash icon on hover; clicking it calls `POST /snapshots/delete-files` and removes the row immediately from the UI (no page reload)
356
+ - **History panel — multi-select delete:** a pencil/edit icon in the panel header enters selection mode; the icon swaps to a recycle bin (confirming delete mode) and a yellow bar appears showing selection count + "Delete selected" + "Cancel"; checkboxes appear on all rows across all workspace sections; "Delete selected" calls `POST /snapshots/delete-files` for all checked items at once
357
+ - **History panel — Clear workspace:** in selection mode, each workspace section shows a "Clear all" button that calls `POST /snapshots/clear-workspace`; deletes all snapshot files inside the workspace but leaves the folder and accordion row visible (empty)
358
+ - **History panel — Delete workspace:** in selection mode, each workspace section shows a "Delete folder" button; after a browser confirm dialog, calls `POST /snapshots/delete-workspace` and removes the workspace section entirely from the panel
359
+ - **Three new server endpoints (v0.12):** `POST /snapshots/delete-files`, `POST /snapshots/clear-workspace`, `POST /snapshots/delete-workspace` — all apply workspace and filename safe-name checks (same rules as `POST /snapshots/load`); `delete-workspace` resets `lastWorkspace` to `""` when the deleted workspace matches
360
+ - **Header layout:** action buttons (pencil/recycle-bin, lock/unlock) right-aligned in the panel header with a vertical separator (`|`) before the close button
361
+ - 16 new server unit tests for the three delete endpoints (172 total)
362
+
363
+ ## 0.11.0 — 2026-06-27
364
+
365
+ - **`render()` and `commit_step_frames()` now return `{ ok: true, id: "<uuid>" }`:** the UUID of the snapshot written for that call — agents can store this to retrieve the diagram later regardless of what else is rendered; `id` is omitted (non-fatal) if the snapshot write fails
366
+ - **`export(id?)` — retrieve any past snapshot by UUID:** `GET /export?id=<uuid>` (REST) and `export({ id })` (MCP tool) scan all workspace snapshot files for the one whose `id` field matches and return its payload; returns `{ ok: false, error: "graph not found" }` if no match; old snapshots without an `id` field are silently non-addressable
367
+ - **Snapshot schema gains `id` field:** `saveSnapshot()` generates a UUID (`crypto.randomUUID()`) at write time and stores it as the first field in the JSON; backward-compatible — old readers ignore the new field
368
+ - **`findSnapshotById(id, dir)` in `server/snapshot-reader.ts`:** scans all workspace subdirectories for a snapshot with a matching `id` field; returns the payload string or `null`
369
+ - **`showcase.js` — Section 12 (`-x` / `--exportid`):** new manual test section that renders two diagrams, verifies `GET /export?id=` retrieves the first by UUID after the canvas has moved on, and confirms 404 for a nonexistent ID
370
+ - **`showcase.js` and `click-demo.js` compliance fix:** all `POST /render` calls in both human-driven test scripts now pass the required `workspace` option (was silently failing since v0.7)
371
+ - 16 new unit tests covering render/commit id-in-response and GET /export id-based lookup (found, not-found, empty-id fallback)
372
+
373
+ ## 0.10.0 — 2026-06-27
374
+
375
+ - **Right-side controls panel:** the history toggle button and Done button now live in a compact fixed panel on the right edge of the viewport (`<div class="controls-panel">`) — always visible, never occluding the canvas; replaces the footer-based `done-bar` from v0.2–v0.9
376
+ - **Done button icon-only:** Done button displays a checkmark SVG icon instead of the text label "Done"; tooltip on hover shows "Done"; the 2s "Sent ✓" text feedback on click is preserved
377
+ - **History panel lock/unlock toggle:** a small lock icon button in the panel header controls auto-close behaviour — unlocked (default): loading a snapshot closes the panel; locked: panel stays open so the user can browse multiple snapshots without reopening; lock state resets when the panel is closed
378
+ - **`lastWorkspace` update on history load:** `POST /snapshots/load` now calls `setLastWorkspace(workspace)` on success so the history panel auto-expands the correct workspace section on next open after a cross-workspace load; `GET /snapshots/all` returns `isCurrent: true` for the loaded workspace
379
+ - 1 new unit test verifying `lastWorkspace` is updated after a cross-workspace snapshot load
380
+
381
+ ## 0.9.0 — 2026-06-26
382
+
383
+ - **Live browser preview on `append_frame()`:** each valid `append_frame()` call now immediately broadcasts the accumulated partial step-frames sequence to the browser, so the user watches the step-through diagram grow one frame at a time — no waiting for `commit_step_frames()`
384
+ - `commit_step_frames()` is now **finalization-only**: assembles the full step-frames JSON, writes the snapshot, updates in-memory canvas state (so `export()` works), cancels any running slideshow, and sends a final broadcast for consistency (handles `clear()` edge cases) — the primary visual was already rendered incrementally by `append_frame()`
385
+ - `export()` before `commit_step_frames()` continues to return the pre-build canvas state; after commit it returns the fully assembled step-frames JSON (unchanged contract)
386
+ - `server/ws.ts` gains `broadcastStepFrames(frames, frameType, currentFrame, title?)` — shared helper used by both `append_frame` and `commit_step_frames`
387
+ - `appendFrame()` in `step-frames-builder.ts` returns enriched success result including `frames`, `frame_type`, and `title` (callers use this to broadcast without a second lookup)
388
+ - REST endpoint `POST /step-frames/:id/frame` mirrors the change (pushes to browser after each valid append)
389
+ - MCP tool descriptions for `append_frame`, `commit_step_frames`, and `init_step_frames` updated to reflect v0.9 behavior
390
+ - 10 new unit tests verifying broadcast calls and canvas-state isolation during incremental builds
391
+ - **e2e fix:** all 14 Playwright tests that were silently failing since v0.7 now pass — the calls to `POST /render` lacked the required `options.workspace` parameter; all 28 e2e tests green
392
+
393
+ ## 0.8.0 — 2026-06-24
394
+
395
+ - **New three-tool protocol for incremental step-frames construction:** `init_step_frames()`, `append_frame()`, `commit_step_frames()` — lets agents build complex step-frames sequences one frame at a time instead of generating one large JSON payload in a single shot
396
+ - `init_step_frames(frame_type, workspace, title?)` — creates an in-memory builder session, pushes a "Building step-frames… 0 frames" placeholder to the browser, returns a UUID
397
+ - `append_frame(id, payload, label?)` — validates payload against `frame_type` (same hard gate as `render()`), appends to the session; invalid frames are rejected without disturbing prior frames
398
+ - `commit_step_frames(id)` — assembles full step-frames JSON, cancels any running slideshow, writes snapshot, broadcasts to browser (identical path to `render(type="step-frames", ...)`); `export()` returns assembled JSON after commit
399
+ - Partial builder sessions expire silently after 30 minutes of inactivity; expired IDs return `{ ok: false, error: "step-frames session not found or expired" }`
400
+ - REST fallback endpoints: `POST /step-frames/init`, `POST /step-frames/:id/frame`, `POST /step-frames/:id/commit`
401
+ - Browser shows a placeholder state ("Building step-frames… N frames") during construction; diagram renders normally after commit
402
+ - `server/validate.ts` now exports `validatePayload()` as a shared helper (used by both the builder and REST/MCP handlers)
403
+ - `server/step-frames-builder.ts` is a new module encapsulating the in-memory builder map with TTL cleanup
404
+ - 32 new automated tests: 16 builder unit tests, 16 REST endpoint tests, 1 Playwright e2e test
405
+
406
+ ## 0.7.0 — 2026-06-15
407
+
408
+ - **Breaking change:** `options.workspace` is now required in both the `render()` MCP tool and `POST /render` REST endpoint; omitting it returns `{ ok: false, error: "workspace is required" }` with HTTP 400 before any render or snapshot write
409
+ - Removed the three-level fallback chain (`options.workspace` → `WHITEBOARD_WORKSPACE` env var → `basename(cwd())`); workspace must always be supplied explicitly by the agent
410
+ - `WHITEBOARD_WORKSPACE` environment variable is no longer read anywhere in the server codebase (deprecated and removed)
411
+ - Server tracks `lastWorkspace` in-memory (updated on every successful `render()` call); `GET /snapshots/all` uses it to determine `isCurrent`, replacing the former env var lookup
412
+ - `RenderOptions.workspace` in `snapshot.ts` changed from optional to required; no fallback inside `saveSnapshot()`
413
+ - All 109 unit tests updated to pass `options.workspace` explicitly; new tests cover the missing-workspace error path and env-var removal
414
+
415
+ ## 0.6.0 — 2026-06-13
416
+
417
+ - `render()` MCP tool and `POST /render` REST endpoint now accept `options.workspace` — an optional string that overrides the snapshot workspace for that call only (precedence: per-call > `WHITEBOARD_WORKSPACE` env var > `basename(cwd())`)
418
+ - Enables per-session snapshot routing without restarting the server (e.g. one machine, multiple courses each passing their own workspace name)
419
+ - Workspace name validation: alphanumeric, dashes, underscores, dots, spaces only — path separators and `..` are rejected with `{ ok: false, error: "..." }`; canvas render proceeds only after validation passes
420
+ - `saveSnapshot()` updated to accept explicit workspace parameter; snapshot files land in `~/.agent-whiteboard/<workspace>/` as usual; history panel scope is unaffected (still uses env var / default)
421
+ - `isValidWorkspaceName()` helper exported from `server/validate.ts` and shared by MCP tool and REST handler
422
+ - 37 new unit tests (workspace validation, snapshot routing, invalid name rejection, step-frames workspace override); 109 unit tests total — all passing
423
+
424
+ ## 0.5.0 — 2026-06-10
425
+
426
+ - History panel now groups snapshots by workspace in a `<details>` accordion; the current workspace section is auto-expanded on open, all others are collapsed
427
+ - `GET /snapshots/all` endpoint: scans all workspace subdirectories under the snapshots root and returns grouped results (`{ ok, workspaces: [{ name, isCurrent, snapshots }] }`)
428
+ - `POST /snapshots/load` extended with optional `workspace` field for cross-workspace loading; workspace name safety check (no path separators, no bare `..`, no null bytes); omitting the field defaults to the current workspace (backward-compatible)
429
+ - `server/snapshot-reader.ts`: new `listAllSnapshots(dir, currentWorkspace)` function; workspaces with no readable snapshots are omitted from the result
430
+ - `client/src/HistoryPanel.svelte`: switched from `GET /snapshots` to `GET /snapshots/all`; accordion UI with `<details>`/`<summary>` elements; "current" badge on the active workspace; snapshot rows pass `{ workspace, filename }` to the load endpoint
431
+ - 11 new unit tests (3 for `GET /snapshots/all`, 6 for the workspace field on `POST /snapshots/load`, 2 backward-compat); 11 new e2e tests; 95 unit tests, 27 e2e tests — all passing
432
+
433
+ ## 0.4.0 — 2026-06-09
434
+
435
+ - Added history navigator: clock toggle button in the browser UI opens a slide-in panel showing past render snapshots
436
+ - `GET /snapshots` endpoint: returns workspace snapshot list sorted newest-first (`{ ok, snapshots: [{ filename, timestamp, type, title? }] }`)
437
+ - `POST /snapshots/load` endpoint: loads a named snapshot onto the canvas without writing a new snapshot file; path traversal protected; same payload hard-gate as `POST /render`
438
+ - `server/snapshot-reader.ts`: new module with `listSnapshots()` (directory scan, malformed-file skipping) and `loadSnapshotContent()` (safe file read)
439
+ - `client/src/HistoryPanel.svelte`: new Svelte component — fetches list on open, shows type badge + title + human-friendly timestamp per row, clicking a row loads the snapshot and closes the panel
440
+ - `client/vite.config.ts`: added `/snapshots` proxy entry so the Vite dev server forwards both `GET /snapshots` and `POST /snapshots/load` to the Node server
441
+ - 22 new unit tests; 7 new e2e tests; 86 unit tests total, all passing
442
+
443
+ ## 0.1.2 — 2026-06-09
444
+
445
+ - Added render snapshot persistence: every successful `render()` call writes a JSON snapshot to `~/.agent-whiteboard/<workspace>/<timestamp>_screen.json`
446
+ - Snapshot schema: `{ timestamp, workspace, type, payload, options }` — captures all renderer types including step-frames
447
+ - Workspace name defaults to `basename(process.cwd())`; overridable via `WHITEBOARD_WORKSPACE` env var
448
+ - Snapshots root defaults to `~/.agent-whiteboard/`; overridable via `WHITEBOARD_SNAPSHOTS_DIR` env var (used by tests to avoid touching the real home directory)
449
+ - Write failures are non-fatal: error logged to stderr, `render()` still returns `{ ok: true }`
450
+ - Invalid renders (validation failure) produce no snapshot file
451
+ - New module `server/snapshot.ts`; hooks in both `server/app.ts` (REST) and `server/mcp.ts` (MCP tool)
452
+ - 6 new unit tests via `vi.mock`; 72 tests total, all passing
453
+
454
+ ## 0.1.1 — 2026-06-08
455
+
456
+ - Extended `POST /wait-click` to accept an optional `node_actions` body (`Record<string, string[]>`), enabling popup menus from REST callers (previously MCP-only)
457
+ - Added `isNodeActionsValid()` guard in `server/app.ts`; invalid payloads return HTTP 400
458
+ - Showcase Section 10 rewritten as a real end-to-end interactive popup demo (Client/Server/DB nodes with per-node action menus)
459
+ - Added Section 11 — edge click demo (`--edge` flag)
460
+ - Composable showcase flags: `-s` (standard 1–8), `-i` (interactive), `-u` (popup), `-e` (edge), `-a` (all); combinable, deduplicated
461
+ - Fixed Section 9 edge-click guard to skip drill-down lookup for edge types
462
+ - 2 new unit tests (66 total, all passing); docs updated to remove MCP-exclusive restriction on popup menus
463
+
464
+ ## 0.1.0 — 2026-06-08
465
+
466
+ - Consolidated all test-related files under a single `tests/` root: `tests/e2e/` (Playwright), `tests/human_driven/` (manual scripts), `tests/unit/server/` (Vitest integration tests), `tests/unit/client/` (placeholder for future Svelte unit tests)
467
+ - Updated `playwright.config.ts` (`testDir`) and `vitest.config.ts` (`include` pattern) to point to new locations
468
+ - Updated import paths in `tests/unit/server/app.test.ts` (`../../../server/*.js`)
469
+ - All 64 Vitest + 16 Playwright tests pass; human-driven showcase and click-demo scripts confirmed working from new paths
470
+ - Added §9 test-restructure proposal to `01_input-ideas.md`, risks to `02_assumptions-and-risks.md`, updated project structure in `04_architecture.md`
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Marco Pugliese
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.