agent-whiteboard 1.1.0 → 1.2.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 (75) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +49 -4
  3. package/dist/client/assets/{abnfDiagram-VCTEODGH-CcuBIZ5h.js → abnfDiagram-VCTEODGH-Dj8sd_5g.js} +1 -1
  4. package/dist/client/assets/{architectureDiagram-5GKGNRK7-CpMRoWIY.js → architectureDiagram-5GKGNRK7-B1QyVL0F.js} +1 -1
  5. package/dist/client/assets/{blockDiagram-I7D4REHJ-Dee0m4lz.js → blockDiagram-I7D4REHJ-CmU2SSFe.js} +1 -1
  6. package/dist/client/assets/{c4Diagram-7LVT6UL2-D_qs3YTM.js → c4Diagram-7LVT6UL2-DWmhp77c.js} +1 -1
  7. package/dist/client/assets/channel-C9ni1Via.js +1 -0
  8. package/dist/client/assets/{chunk-2Q5K7J3B-CBwQ-uNF.js → chunk-2Q5K7J3B-CQLo8Sbv.js} +1 -1
  9. package/dist/client/assets/{chunk-5VM5RSS4-yGvVHapR.js → chunk-5VM5RSS4-fJTWmTTt.js} +1 -1
  10. package/dist/client/assets/{chunk-F27PBJKO-BzFUf98n.js → chunk-F27PBJKO-DqzEf5gi.js} +1 -1
  11. package/dist/client/assets/{chunk-IMKFNOWR-D-Zj1GaL.js → chunk-IMKFNOWR-CMamLTz3.js} +1 -1
  12. package/dist/client/assets/{chunk-JWPE2WC7-C8Aj4MVR.js → chunk-JWPE2WC7-tyYPyD43.js} +1 -1
  13. package/dist/client/assets/{chunk-POPQ4Y6H-DZP2a-yV.js → chunk-POPQ4Y6H-XdY4rO_V.js} +1 -1
  14. package/dist/client/assets/{chunk-SVP7TREG-COihS1MQ.js → chunk-SVP7TREG-COvqzqaO.js} +1 -1
  15. package/dist/client/assets/{chunk-TICWLB2K-DNZTRqyJ.js → chunk-TICWLB2K-BsJSFuPC.js} +1 -1
  16. package/dist/client/assets/{chunk-XXDRQBXY-BC_9L4PF.js → chunk-XXDRQBXY-HfVrYBJb.js} +1 -1
  17. package/dist/client/assets/classDiagram-ZZMXUADV-DktNEiGC.js +1 -0
  18. package/dist/client/assets/classDiagram-v2-VYDZK3BY-DktNEiGC.js +1 -0
  19. package/dist/client/assets/{cose-bilkent-JH36ORCC-DrJ8c1cV.js → cose-bilkent-JH36ORCC-UXGdwSjS.js} +1 -1
  20. package/dist/client/assets/{cynefin-OW5HDTMX-CMVAR4fd.js → cynefin-OW5HDTMX-PWlPYnpn.js} +1 -1
  21. package/dist/client/assets/{cynefinDiagram-5FMLGOSQ-CdAQ0Lst.js → cynefinDiagram-5FMLGOSQ-CYE5H1wR.js} +1 -1
  22. package/dist/client/assets/{dagre-GXQ25YYZ-CybK0L_u.js → dagre-GXQ25YYZ-DaknBOKq.js} +1 -1
  23. package/dist/client/assets/{diagram-S7CK7UJ4-CHjTv5Sr.js → diagram-S7CK7UJ4-Dz7rZHxF.js} +1 -1
  24. package/dist/client/assets/{diagram-UQ7AKVKN-DfypPsGR.js → diagram-UQ7AKVKN-DzCi6tiS.js} +1 -1
  25. package/dist/client/assets/{diagram-VSXAHHWV-DHsmmCug.js → diagram-VSXAHHWV-TlUzhXgv.js} +1 -1
  26. package/dist/client/assets/{diagram-VX7I27RA-CHZoTi9k.js → diagram-VX7I27RA-Ds7jdzK7.js} +1 -1
  27. package/dist/client/assets/{diagram-Z3DM3KII-Ceb2h13y.js → diagram-Z3DM3KII-C--JD7cf.js} +1 -1
  28. package/dist/client/assets/{ebnfDiagram-PWID7BFC-BlZ247wB.js → ebnfDiagram-PWID7BFC-Dw5OM-dL.js} +1 -1
  29. package/dist/client/assets/{erDiagram-RLTQ6QDP-BQdw-U-S.js → erDiagram-RLTQ6QDP-DXalBHjA.js} +1 -1
  30. package/dist/client/assets/{flowDiagram-HODETNUW-DjUWHA94.js → flowDiagram-HODETNUW-DcI3UqyE.js} +1 -1
  31. package/dist/client/assets/{ganttDiagram-EL5Y4UJY-CXu2s5vq.js → ganttDiagram-EL5Y4UJY-DyrkIo6c.js} +1 -1
  32. package/dist/client/assets/{gitGraphDiagram-WWUBYQGX-CwlsB28k.js → gitGraphDiagram-WWUBYQGX-fJKfGRcg.js} +1 -1
  33. package/dist/client/assets/index-CIhkXT_z.js +19 -0
  34. package/dist/client/assets/index-DEiH2AE7.css +1 -0
  35. package/dist/client/assets/{infoDiagram-27XIBGKW-C7nvTbsQ.js → infoDiagram-27XIBGKW-Cijiqswb.js} +1 -1
  36. package/dist/client/assets/{ishikawaDiagram-5VMMS53U-DGFXG874.js → ishikawaDiagram-5VMMS53U-C1WqWU2l.js} +1 -1
  37. package/dist/client/assets/{journeyDiagram-3NMN7TZE-BDdRMcmq.js → journeyDiagram-3NMN7TZE-7j05mbLR.js} +1 -1
  38. package/dist/client/assets/{kanban-definition-UXKFOSKX-C3fnvfeO.js → kanban-definition-UXKFOSKX-D5mGDfs_.js} +1 -1
  39. package/dist/client/assets/{layout-BfegUiqt.js → layout-KEin06-H.js} +1 -1
  40. package/dist/client/assets/{mermaid.core-DioArgDk.js → mermaid.core-CLvSDmrA.js} +5 -5
  41. package/dist/client/assets/{mindmap-definition-YA3MSWOX-CFCbAUC0.js → mindmap-definition-YA3MSWOX-BsCqKd_d.js} +1 -1
  42. package/dist/client/assets/{pegDiagram-XKGWAZYB-B1ULJH6S.js → pegDiagram-XKGWAZYB-Du5lDU3W.js} +1 -1
  43. package/dist/client/assets/{pieDiagram-E7YTZNPT-B9H3HosU.js → pieDiagram-E7YTZNPT-hjv5Ao6B.js} +1 -1
  44. package/dist/client/assets/{quadrantDiagram-AXDQQJYC-eWc3HHkM.js → quadrantDiagram-AXDQQJYC-DNon8LWK.js} +1 -1
  45. package/dist/client/assets/{railroadDiagram-O6MQD6OU-DT7c-KwW.js → railroadDiagram-O6MQD6OU-yNZz2zxo.js} +1 -1
  46. package/dist/client/assets/{requirementDiagram-BXWQKSXE-Dk1yVWV5.js → requirementDiagram-BXWQKSXE-CZiiEUHW.js} +1 -1
  47. package/dist/client/assets/{sankeyDiagram-P5KCCOFB-B5a__hiX.js → sankeyDiagram-P5KCCOFB-D-u8ZQUV.js} +1 -1
  48. package/dist/client/assets/{sequenceDiagram-WJ2MYXX4-CkCIByRS.js → sequenceDiagram-WJ2MYXX4-BxjTf3q9.js} +1 -1
  49. package/dist/client/assets/{sizeCapture-INFHLROL-CnZIW94r.js → sizeCapture-INFHLROL-Dt4Wg8C5.js} +1 -1
  50. package/dist/client/assets/{stateDiagram-D77RDMKH-89Sfaoz9.js → stateDiagram-D77RDMKH-B8BM6dln.js} +1 -1
  51. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-MJauX84T.js +1 -0
  52. package/dist/client/assets/{swimlanes-42K2YHIH-C7m88XIh.js → swimlanes-42K2YHIH-eGWrsWAA.js} +1 -1
  53. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-BYFu8tyL.js +8 -0
  54. package/dist/client/assets/{timeline-definition-24CTP7MA-BHKHgHq9.js → timeline-definition-24CTP7MA-CAM-3Qj9.js} +1 -1
  55. package/dist/client/assets/{vennDiagram-4TSXK5OY-BqKaRdWu.js → vennDiagram-4TSXK5OY-CuFEwwD0.js} +1 -1
  56. package/dist/client/assets/{wardleyDiagram-VM6X3IG4-XiDVRZV5.js → wardleyDiagram-VM6X3IG4-DI5yCVh0.js} +1 -1
  57. package/dist/client/assets/{xychartDiagram-S5SC5T6Z-CxP4dIVd.js → xychartDiagram-S5SC5T6Z-BI0Bn-OQ.js} +1 -1
  58. package/dist/client/index.html +2 -2
  59. package/dist/server/app.js +2 -0
  60. package/dist/server/export-html.js +4 -0
  61. package/dist/server/mcp.js +19 -4
  62. package/dist/server/quiz-submission.js +116 -0
  63. package/dist/server/quiz.js +110 -0
  64. package/dist/server/render-core.js +3 -2
  65. package/dist/server/routes/quiz.js +12 -0
  66. package/dist/server/routes/render.js +4 -4
  67. package/dist/server/validate.js +14 -0
  68. package/package.json +1 -1
  69. package/dist/client/assets/channel-9qggtmY_.js +0 -1
  70. package/dist/client/assets/classDiagram-ZZMXUADV-BUnGzX-0.js +0 -1
  71. package/dist/client/assets/classDiagram-v2-VYDZK3BY-BUnGzX-0.js +0 -1
  72. package/dist/client/assets/index-CFelVSRu.js +0 -19
  73. package/dist/client/assets/index-GF8Uj64S.css +0 -1
  74. package/dist/client/assets/stateDiagram-v2-MP3YSRHH-CP--cdl-.js +0 -1
  75. package/dist/client/assets/swimlanesDiagram-VR7AAH4N-DmG2scPu.js +0 -8
package/CHANGELOG.md CHANGED
@@ -1,3 +1,24 @@
1
+ ## 1.2.0 — 2026-11-06
2
+
3
+ **Milestone v1.8 — Quiz Support (Sprints 89–91) complete.** Adds interactive quizzes for reviewing the user's knowledge (FR33 in `01`, F44–F50/NF57 in `03`).
4
+
5
+ - **`quiz` render type (F44, F45):** `render({ type: "quiz", ... })` takes a JSON payload of single- and multiple-choice questions, validated by the new `server/quiz.ts` (unique ids, option counts, weight sums). Mode is derived from the answer key: every question keyed → inline, none → deferred, a mix is rejected. Valid in `render()` only, not in slideshows or step-frames.
6
+ - **Scoring (F47):** per question `all_or_nothing` (exact correct set) or `partial` (multiple-choice only; weighted correct options summing to 1, wrong picks earn nothing), with optional `points`.
7
+ - **Quiz renderer + inline evaluation (F44, F46):** new `Quiz.svelte` with radio/checkbox controls and an in-quiz Submit, disabled until every question is answered. Inline quizzes show per-question results and the total score after Submit.
8
+ - **Deferred evaluation (F48, F49):** Submit posts to the new `POST /quiz/submit`; the new `wait_quiz` MCP tool / `POST /wait-quiz` long-poll returns the questions and answers (plus the client-computed score for inline quizzes). 10-minute timeout, submit-before-wait retention, and superseding like `wait_click`.
9
+ - **History & export (F50):** quizzes are snapshotted as questions only and reload fresh; HTML export replaces a quiz with an explanatory note and never leaks its questions or answer key.
10
+ - **Dev/tooling:** `/quiz` added to the Vite dev proxy; README documents the quiz format, `wait_quiz` and the new endpoints; showcase Section 15 (`-q`) demonstrates inline and deferred quizzes.
11
+ - Full suite: 745 unit tests passing; `tsc`/`svelte-check`/`eslint`/`npm run build` clean.
12
+
13
+ ## 1.1.1 — 2026-11-06
14
+
15
+ **Milestone v1.7 — Unified Import/Export Button (Sprints 87–88) complete.** Pure UI consolidation; no server, API, MCP or snapshot-format changes.
16
+
17
+ - **Showcase page (F43):** `mockup/import-export-button-options.html` presented icon/tooltip/menu-label candidates; chosen: opposing-arrows icon, "Import / export" tooltip, "Export…" / "Import…" labels.
18
+ - **Single toolbar button (F41):** the separate Export and Import buttons are replaced by one `ImportExportMenu.svelte` button opening a popover with "Export…" (existing export modal) and "Import…" (existing `ImportModal`). Delete and drag-and-drop `.zip` import are unchanged.
19
+ - **ARIA menu behaviour (F42):** `aria-haspopup`/`aria-expanded`, `role="menuitem"`, arrow/Escape handling, focus return to the button, outside-click and Tab dismissal.
20
+ - **Tests (NF56):** new `ImportExportMenu` unit tests plus updated `App` tests.
21
+
1
22
  ## 1.1.0 — 2026-09-07
2
23
 
3
24
  **Milestone v1.6 — Workspace Export/Import for Sharing (Sprints 85–86) complete.** Adds a full workspace sharing round-trip: export a workspace as a portable `.zip` and import it into another whiteboard instance (FR31 in `01`, F34–F40/NF55 in `03`). The first milestone to ship real new user-facing functionality since 1.0 — correctly a minor bump per the versioning rule in `05`.
package/README.md CHANGED
@@ -8,7 +8,7 @@ The original goal was simple: instead of explaining everything through long text
8
8
 
9
9
  As the project evolved, it became clear that the same interaction model is useful far beyond education. Whether an agent is explaining a software architecture, walking through a network protocol, debugging a problem, presenting data, or documenting a system, the ability to communicate visually is just as valuable. Agent Whiteboard is therefore intentionally domain-agnostic.
10
10
 
11
- Through a set of MCP tools, an AI agent can render Mermaid diagrams, SVG, HTML, LaTeX, and Vega-Lite charts, build incremental multi-step explanations, pause until the user is ready to continue, react to clicks on diagrams, and keep a persistent history of snapshots that can later be reloaded or exported.
11
+ Through a set of MCP tools, an AI agent can render Mermaid diagrams, SVG, HTML, LaTeX, Vega-Lite charts, and interactive quizzes, build incremental multi-step explanations, pause until the user is ready to continue, react to clicks on diagrams, and keep a persistent history of snapshots that can later be reloaded or exported.
12
12
 
13
13
  Unlike collaborative whiteboard applications, Agent Whiteboard is not a canvas that humans edit directly. The whiteboard is controlled by the AI agent, making it a lightweight visual communication surface between the agent and its user. Everything runs locally and integrates naturally with Claude Code through MCP.
14
14
 
@@ -23,13 +23,13 @@ The browser also has a History panel for browsing, reloading, deleting, and expo
23
23
  ```
24
24
  [Claude Code agent]
25
25
  │
26
- └── 13 MCP tools — render, step-frames builder, slideshow, wait_click/wait_done, snapshot export (see below)
26
+ └── 15 MCP tools — render, step-frames builder, slideshow, wait_click/wait_done/wait_quiz, snapshot export (see below)
27
27
  │
28
28
  ▼
29
29
  [Node.js server :3000] — MCP over SSE + REST fallback + WebSocket
30
30
  │
31
31
  ▼
32
- [Browser tab :5173] — Svelte SPA, auto-opens on start, with a History panel for browsing/deleting/exporting past snapshots
32
+ [Browser tab :5173] — Svelte SPA, auto-opens on start, with a History panel for browsing/deleting/exporting/importing past snapshots
33
33
  ```
34
34
 
35
35
  The server exposes MCP tools over SSE at `http://localhost:3000/mcp`. Claude Code connects automatically when `.mcp.json` is present in the project root.
@@ -100,6 +100,7 @@ Push content to the whiteboard canvas. Replaces whatever is currently on screen.
100
100
  | `"html"` | HTML/CSS fragment. Sanitized via DOMPurify — inline styles only; `<script>` and `<style>` tags are stripped. |
101
101
  | `"katex"` | LaTeX string, rendered in display mode. |
102
102
  | `"vega-lite"` | Vega-Lite JSON spec (must be valid JSON string). |
103
+ | `"quiz"` | JSON quiz — see [Quizzes](#quizzes). Valid in `render()` only (not in `slideshow()` or step-frames). |
103
104
 
104
105
  `render()` is single-frame only. For a step-through sequence (multiple frames navigable via `step()`/`seek()`), use [`init_step_frames`/`append_frame`/`commit_step_frames`](#init_step_framesframe_type-workspace-title) instead — see [Step-frames sequences](#step-frames-sequences).
105
106
 
@@ -161,6 +162,32 @@ Block until the user clicks the **Done** button in the browser tab, then return.
161
162
 
162
163
  <img src="docs/screenshots/done-button.png" width="700" alt="Done button visible in the controls panel while wait_done() is armed">
163
164
 
165
+ ### `wait_quiz()`
166
+
167
+ Block until the user clicks **Submit** on the quiz currently on screen (rendered with `render({ type: "quiz", ... })`), then return the questions and the user's answers. A submission made before you call `wait_quiz()` is kept and returned immediately, as long as that quiz is still on screen. Times out after 10 minutes; a second `wait_quiz()` supersedes a pending one.
168
+
169
+ **Returns:** `{ "ok": true, "type": "submitted", "questions": {...}, "answers": [{ "questionId": "q1", "selected": ["a"] }] }` — plus `"score": { "earned", "possible" }` for an inline quiz. Also `{ "ok": true, "type": "timeout" | "superseded" }`, or `{ "ok": false, "error": "..." }` if no quiz is on the canvas.
170
+
171
+ ### Quizzes
172
+
173
+ A quiz is a JSON payload of single- or multiple-choice questions:
174
+
175
+ ```json
176
+ { "title": "Check-in",
177
+ "questions": [
178
+ { "id": "q1", "text": "Which verb is idempotent?", "kind": "single",
179
+ "options": [{ "id": "a", "text": "PUT", "correct": true }, { "id": "b", "text": "POST" }] },
180
+ { "id": "q2", "text": "Pick the primes", "kind": "multiple", "scoring": "partial", "points": 2,
181
+ "options": [{ "id": "a", "text": "2", "correct": true, "weight": 0.5 }, { "id": "b", "text": "3", "correct": true, "weight": 0.5 }, { "id": "c", "text": "4" }] }
182
+ ] }
183
+ ```
184
+
185
+ - **Inline evaluation** — mark `correct: true` on the right option(s) of *every* question. The browser scores the quiz when the user clicks Submit and shows per-question results plus the total (the answer key is visible to anyone inspecting the page, so use deferred mode for anything you don't want revealed).
186
+ - **Deferred evaluation** — mark none. Submit sends the answers to the agent, which receives them via `wait_quiz()` and evaluates them itself.
187
+ - A payload where only some questions have a key is rejected.
188
+ - **Scoring** (per question): `"all_or_nothing"` (default) earns `points` (default 1) only for the exact correct set. `"partial"` (multiple-choice only) gives every correct option a `weight`; the weights must sum to 1, the score is the sum of the weights of the correct options selected, and wrong picks earn nothing.
189
+ - Quizzes are saved to history (questions only — answers and scores are never persisted) and reload as a fresh quiz. They are not included in HTML exports.
190
+
164
191
  ### `wait_click([node_actions])`
165
192
 
166
193
  Arm the browser for a single node or edge click on the current Mermaid diagram. Nodes are highlighted with a blue outline and pointer cursor. Applies reliably to `graph`/`flowchart` diagrams; other types are best-effort.
@@ -247,6 +274,14 @@ init_step_frames(frame_type="mermaid", workspace="my-lesson", title="TCP handsha
247
274
 
248
275
  `wait_done()` isn't coupled to step-frames — it just blocks on the Done button — so this pattern works with any number of frames and needs no dedicated tool support.
249
276
 
277
+ Knowledge check — render a quiz and wait for the user to submit it (see [Quizzes](#quizzes)):
278
+
279
+ ```
280
+ render(type="quiz", payload='{"questions":[{"id":"q1","text":"What does SYN start?","kind":"single","options":[{"id":"a","text":"A handshake"},{"id":"b","text":"A teardown"}]}]}', options={ workspace: "my-lesson" })
281
+ → wait_quiz() # no answer key in the payload → deferred: the answers come back to the agent
282
+ → evaluate result.answers, explain the right answers, and score them
283
+ ```
284
+
250
285
  ## REST fallback
251
286
 
252
287
  All tools have HTTP equivalents for scripting or testing without an MCP client:
@@ -262,6 +297,8 @@ All tools have HTTP equivalents for scripting or testing without an MCP client:
262
297
  | `POST /seek` | `{ "frame": N }` |
263
298
  | `POST /user-done` | — (simulates the Done button click) |
264
299
  | `POST /wait-done` | — (long-polls until Done is signalled) |
300
+ | `POST /quiz/submit` | `{ "answers": [{ "questionId": "...", "selected": ["optionId", ...] }], "score": { "earned": N, "possible": N } (optional, inline quizzes only) }` (sent by browser) — 400 if no quiz is on screen or the answers don't match it |
301
+ | `POST /wait-quiz` | — (long-polls until the quiz is submitted; REST twin of `wait_quiz`) |
265
302
  | `POST /node-click` | `{ "type": "node"\|"edge", "id": "...", "label": "..." }` (sent by browser) |
266
303
  | `POST /wait-click` | `{ "node_actions": { "<id>": ["action1", ...] } }` (optional; long-polls until a node/edge click is signalled) |
267
304
  | `POST /step-frames/init` | `{ "frame_type": "...", "workspace": "...", "title": "..." }` — starts an incremental builder, returns `{ "ok": true, "id": "<uuid>" }` |
@@ -273,12 +310,16 @@ All tools have HTTP equivalents for scripting or testing without an MCP client:
273
310
  | `POST /snapshots/delete-files` | `{ "workspace": "...", "filenames": ["..._screen.json", ...] }` — deletes specific snapshot files |
274
311
  | `POST /snapshots/delete-workspace` | `{ "workspace": "..." }` — deletes an entire workspace's snapshot folder |
275
312
  | `POST /export-html` | `{ "items": [{ "workspace": "...", "id": "..." }, ...], "mode": "cdn" \| "offline" }` (`mode` optional, defaults to `"cdn"`) — returns the assembled HTML file as a download, not JSON |
313
+ | `POST /export-zip` | `{ "items": [{ "workspace": "...", "id": "..." }, ...] }` — returns a portable `.zip` (the selected snapshots' raw JSON plus a `manifest.json`) as a download, for sharing a workspace |
314
+ | `POST /import` | multipart form: `file` (a `.zip` from `/export-zip`), `mode` (`"create"` \| `"merge"`, default `"create"`), `targetWorkspace` — imports snapshots, deduplicating by snapshot `id`/`timestamp`; 50MB cap |
276
315
 
277
316
  All endpoints return the same JSON shapes as the MCP tools, except `POST /export-html`, which streams the HTML file directly (`Content-Disposition: attachment`). `mode: "cdn"` (default) links Mermaid/Bootstrap/KaTeX from a CDN; `mode: "offline"` embeds them for offline reading, at the cost of a much larger file (the Mermaid bundle alone is ~3.3MB).
278
317
 
279
318
  ## History & snapshots
280
319
 
281
- Every `render()` / `commit_step_frames()` call writes a JSON snapshot to `~/.agent-whiteboard/<workspace>/` (override with the `WHITEBOARD_SNAPSHOTS_DIR` environment variable). The browser's right-side controls panel has a history toggle that opens a panel for browsing and loading past snapshots grouped by workspace, plus delete and export icon buttons that open a 2-step modal (`client/src/DeleteExportModal.svelte`) for deleting or exporting an entire workspace or a chosen subset of snapshots. Destructive actions require a second confirming click. Exports are produced by the `export_html()` tool / `POST /export-html` endpoint described above.
320
+ Every `render()` / `commit_step_frames()` call writes a JSON snapshot to `~/.agent-whiteboard/<workspace>/` (override with the `WHITEBOARD_SNAPSHOTS_DIR` environment variable). The browser's right-side controls panel has a history toggle that opens a panel for browsing and loading past snapshots grouped by workspace, plus a delete button that opens a 2-step modal (`client/src/DeleteExportModal.svelte`) for deleting an entire workspace or a chosen subset of snapshots. Destructive actions require a second confirming click.
321
+
322
+ A single **Import / export** button (opposing-arrows icon) opens a small keyboard-accessible menu with two entries: **Export…** opens the same modal for exporting a workspace or subset of snapshots as self-contained HTML (the `export_html()` tool / `POST /export-html` endpoint described above) or as a portable `.zip` (`POST /export-zip`), and **Import…** opens a dialog for loading such a `.zip` into this or another whiteboard instance (`POST /import`, merging or creating a new workspace). Dropping a `.zip` anywhere on the page also starts an import.
282
323
 
283
324
  | Step 1 — choose a workspace | Step 2 — delete requires a second confirming click |
284
325
  |----------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------|
@@ -295,6 +336,7 @@ Exercises every renderer and interactive feature (requires `npm run dev` running
295
336
  ```bash
296
337
  node tests/human_driven/showcase.js # Sections 1–8: renderer slideshow + seek
297
338
  node tests/human_driven/showcase.js -x # Section 12: export-by-id (v0.11)
339
+ node tests/human_driven/showcase.js -q # Section 15: quiz, inline + deferred (v1.8)
298
340
  node tests/human_driven/showcase.js -a # All sections
299
341
  ```
300
342
 
@@ -306,6 +348,9 @@ Section flags (combinable):
306
348
  -u, --popup Section 10: node_actions popup menu
307
349
  -e, --edge Section 11: edge click demo
308
350
  -x, --exportid Section 12: export by graph ID (v0.11)
351
+ -c, --incremental Section 13: incremental step-frames creation (init/append/commit)
352
+ -n, --nodetoframe Section 14: node_to_frame autonomous navigation
353
+ -q, --quiz Section 15: quiz — inline scoring + deferred wait_quiz (v1.8)
309
354
  -a, --all All sections
310
355
  -p, --port <port> Server port (default: 3000)
311
356
  -d, --delay <ms> Delay between slides (default: 5000)
@@ -1 +1 @@
1
- import{g as p,r as u,d as a}from"./chunk-SVP7TREG-COihS1MQ.js";import{p as f}from"./chunk-JWPE2WC7-C8Aj4MVR.js";import{_ as n,l as o}from"./mermaid.core-DioArgDk.js";import{M as c,b as d}from"./cynefin-OW5HDTMX-CMVAR4fd.js";import"./index-CFelVSRu.js";import"./step-BEQzeS8V.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},F={parser:R,db:a,renderer:u,styles:p};export{F as diagram};
1
+ import{g as p,r as u,d as a}from"./chunk-SVP7TREG-COvqzqaO.js";import{p as f}from"./chunk-JWPE2WC7-tyYPyD43.js";import{_ as n,l as o}from"./mermaid.core-CLvSDmrA.js";import{M as c,b as d}from"./cynefin-OW5HDTMX-PWlPYnpn.js";import"./index-CIhkXT_z.js";import"./step-BEQzeS8V.js";var v=d().RailroadAbnf.parser.LangiumParser,i=n(e=>{const r=e.alternatives.map(g);return r.length===1?r[0]:{type:"choice",alternatives:r}},"transformAlternation"),g=n(e=>{const r=e.elements.map(y);return r.length===1?r[0]:{type:"sequence",elements:r}},"transformConcatenation"),b=n(e=>{if(e.includes("*")){const[t,s]=e.split("*"),l=t?parseInt(t,10):0,m=s?parseInt(s,10):1/0;return{min:l,max:m}}const r=parseInt(e,10);return{min:r,max:r}},"parseRepeat"),y=n(e=>{const r=A(e.primary);if(!e.repeat)return r;const{min:t,max:s}=b(e.repeat);return t===0&&s===1?{type:"optional",element:r}:{type:"repetition",element:r,min:t,max:s}},"transformElement"),A=n(e=>{switch(e.$type){case"AbnfStringLiteral":return{type:"terminal",value:e.value};case"AbnfNumVal":return{type:"terminal",value:e.value};case"AbnfRuleName":return{type:"nonterminal",name:e.name};case"AbnfGroup":return i(e.element);case"AbnfOptionalGroup":return{type:"optional",element:i(e.element)};default:throw new Error(`Unsupported ABNF primary node: ${e.$type}`)}},"transformPrimary"),P=n(e=>({name:e.name,definition:i(e.definition)}),"transformRule"),h=n(e=>{f(e,a),e.title&&a.setTitle(e.title),e.rules.map(r=>a.addRule(P(r)))},"populateDb"),R={parse:n(e=>{a.clear(),o.debug("[ABNF Parser] Starting Langium parse");const r=v.parse(e);if(r.lexerErrors.length>0||r.parserErrors.length>0)throw new c(r);const t=r.value;o.debug("[ABNF Parser] Parsed rules:",t.rules.length),h(t),o.debug("[ABNF Parser] Parse complete")},"parse"),parser:{yy:a}},F={parser:R,db:a,renderer:u,styles:p};export{F as diagram};