@zenuml/core 3.47.9 → 3.48.1

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 (205) hide show
  1. package/dist/cli/zenuml.mjs +13529 -0
  2. package/dist/cli/zenuml.mjs.map +1 -0
  3. package/dist/cloud-icons-eHuugVSv.js.map +1 -0
  4. package/dist/zenuml.esm.mjs +2153 -2156
  5. package/dist/zenuml.esm.mjs.map +1 -0
  6. package/dist/zenuml.js +82 -82
  7. package/dist/zenuml.js.map +1 -0
  8. package/package.json +18 -5
  9. package/.agents/skills/babysit-pr/SKILL.md +0 -223
  10. package/.agents/skills/babysit-pr/agents/openai.yaml +0 -7
  11. package/.agents/skills/dia-scoring/SKILL.md +0 -139
  12. package/.agents/skills/dia-scoring/agents/openai.yaml +0 -7
  13. package/.agents/skills/dia-scoring/references/selectors-and-keys.md +0 -253
  14. package/.agents/skills/land-pr/SKILL.md +0 -120
  15. package/.agents/skills/propagate-core-release/SKILL.md +0 -205
  16. package/.agents/skills/propagate-core-release/agents/openai.yaml +0 -7
  17. package/.agents/skills/propagate-core-release/references/downstreams.md +0 -42
  18. package/.agents/skills/ship-branch/SKILL.md +0 -105
  19. package/.agents/skills/submit-branch/SKILL.md +0 -76
  20. package/.agents/skills/validate-branch/SKILL.md +0 -72
  21. package/.claude/commands/README.md +0 -162
  22. package/.claude/commands/analyze.md +0 -101
  23. package/.claude/commands/clarify.md +0 -158
  24. package/.claude/commands/code-review.md +0 -322
  25. package/.claude/commands/constitution.md +0 -73
  26. package/.claude/commands/create-docs.md +0 -309
  27. package/.claude/commands/full-context.md +0 -121
  28. package/.claude/commands/gemini-consult.md +0 -164
  29. package/.claude/commands/handoff.md +0 -146
  30. package/.claude/commands/implement.md +0 -56
  31. package/.claude/commands/plan.md +0 -43
  32. package/.claude/commands/refactor.md +0 -188
  33. package/.claude/commands/specify.md +0 -21
  34. package/.claude/commands/tasks.md +0 -62
  35. package/.claude/commands/update-docs.md +0 -314
  36. package/.claude/hooks/README.md +0 -270
  37. package/.claude/hooks/config/sensitive-patterns.json +0 -86
  38. package/.claude/hooks/gemini-context-injector.sh +0 -129
  39. package/.claude/hooks/mcp-security-scan.sh +0 -147
  40. package/.claude/hooks/notify.sh +0 -103
  41. package/.claude/hooks/setup/hook-setup.md +0 -96
  42. package/.claude/hooks/setup/settings.json.template +0 -63
  43. package/.claude/hooks/sounds/complete.wav +0 -0
  44. package/.claude/hooks/sounds/input-needed.wav +0 -0
  45. package/.claude/hooks/subagent-context-injector.sh +0 -65
  46. package/.claude/skills/babysit-pr/SKILL.md +0 -223
  47. package/.claude/skills/babysit-pr/agents/openai.yaml +0 -7
  48. package/.claude/skills/dia-scoring/SKILL.md +0 -139
  49. package/.claude/skills/dia-scoring/agents/openai.yaml +0 -7
  50. package/.claude/skills/dia-scoring/references/selectors-and-keys.md +0 -253
  51. package/.claude/skills/emoji-eval/SKILL.md +0 -187
  52. package/.claude/skills/land-pr/SKILL.md +0 -120
  53. package/.claude/skills/propagate-core-release/SKILL.md +0 -205
  54. package/.claude/skills/propagate-core-release/agents/openai.yaml +0 -7
  55. package/.claude/skills/propagate-core-release/references/downstreams.md +0 -42
  56. package/.claude/skills/ship-branch/SKILL.md +0 -105
  57. package/.claude/skills/submit-branch/SKILL.md +0 -76
  58. package/.claude/skills/validate-branch/SKILL.md +0 -72
  59. package/.claude/skills/zenuml-ux-research/SKILL.md +0 -183
  60. package/.claude/skills/zenuml-ux-research/references/assertion-catalog.md +0 -261
  61. package/.claude/skills/zenuml-ux-research/references/best-practices-overview.md +0 -56
  62. package/.claude/skills/zenuml-ux-research/references/report-template.md +0 -89
  63. package/.claude/skills/zenuml-ux-research/references/scenarios/edit-message-label.md +0 -37
  64. package/.claude/skills/zenuml-ux-research/references/scenarios/insert-message.md +0 -36
  65. package/.claude/skills/zenuml-ux-research/references/scenarios/insert-participant.md +0 -31
  66. package/.claude/skills/zenuml-ux-research/references/scenarios/rename-participant.md +0 -33
  67. package/.claude/skills/zenuml-ux-research/references/scenarios/undo-insert.md +0 -35
  68. package/.devcontainer/devcontainer.json +0 -21
  69. package/.dockerignore +0 -19
  70. package/.eslintrc.js +0 -39
  71. package/.git-blame-ignore-revs +0 -6
  72. package/.kiro/hooks/README.md +0 -38
  73. package/.kiro/hooks/session-sound-notification.js +0 -44
  74. package/.kiro/hooks/session-sound-notification.json +0 -23
  75. package/.mcp.json.example +0 -17
  76. package/.nvmrc +0 -1
  77. package/.prettierignore +0 -4
  78. package/.prettierrc +0 -1
  79. package/.specify/memory/constitution.md +0 -33
  80. package/.specify/scripts/bash/check-prerequisites.sh +0 -166
  81. package/.specify/scripts/bash/common.sh +0 -113
  82. package/.specify/scripts/bash/create-new-feature.sh +0 -97
  83. package/.specify/scripts/bash/setup-plan.sh +0 -60
  84. package/.specify/scripts/bash/update-agent-context.sh +0 -728
  85. package/.specify/templates/agent-file-template.md +0 -23
  86. package/.specify/templates/plan-template.md +0 -219
  87. package/.specify/templates/spec-template.md +0 -116
  88. package/.specify/templates/tasks-template.md +0 -127
  89. package/.storybook/main.ts +0 -25
  90. package/.storybook/preview.ts +0 -29
  91. package/.watchmanconfig +0 -3
  92. package/AGENTS.md +0 -26
  93. package/CLAUDE.md +0 -124
  94. package/DEPLOYMENT.md +0 -62
  95. package/Dockerfile +0 -36
  96. package/IMPLEMENTATION_PLAN.md +0 -163
  97. package/Integration/vanilla-js/index.html +0 -42
  98. package/MCP-ASSISTANT-RULES.md +0 -85
  99. package/README_CN.md +0 -15
  100. package/TUTORIAL.md +0 -116
  101. package/antlr/antlr-4.11.1-complete.jar +0 -0
  102. package/bun.lock +0 -1544
  103. package/bunfig.toml +0 -52
  104. package/docs/UNICODE_SUPPORT.md +0 -179
  105. package/docs/ai-context/deployment-infrastructure.md +0 -21
  106. package/docs/ai-context/docs-overview.md +0 -89
  107. package/docs/ai-context/handoff.md +0 -174
  108. package/docs/ai-context/project-structure.md +0 -160
  109. package/docs/ai-context/system-integration.md +0 -21
  110. package/docs/asciidoc/contributor.adoc +0 -54
  111. package/docs/asciidoc/create-my-own-theme.adoc +0 -149
  112. package/docs/asciidoc/images/creation-component.png +0 -0
  113. package/docs/asciidoc/images/creation-rtl.png +0 -0
  114. package/docs/asciidoc/images/message-arrow-rtl.png +0 -0
  115. package/docs/asciidoc/images/occurrence.png +0 -0
  116. package/docs/asciidoc/images/return-message-conflict.png +0 -0
  117. package/docs/asciidoc/images/shift-up-half-the-height.png +0 -0
  118. package/docs/asciidoc/images/three-layer-info-arch.png +0 -0
  119. package/docs/asciidoc/images/vertical-alignment.svg +0 -1
  120. package/docs/asciidoc/images/vertically-aligning.png +0 -0
  121. package/docs/asciidoc/index.adoc +0 -277
  122. package/docs/asciidoc/theme-debug-web-app.png +0 -0
  123. package/docs/asciidoc/tutorial.adoc +0 -22
  124. package/docs/asciidoc/user-css.png +0 -0
  125. package/docs/async-vs-sync-parser-rules.md +0 -81
  126. package/docs/divider-parser-allow-spaces.md +0 -38
  127. package/docs/highlighting-messages.md +0 -52
  128. package/docs/images/editor-sample.png +0 -0
  129. package/docs/inherited-vs-provided-from.md +0 -64
  130. package/docs/parser/Assignment.md +0 -8
  131. package/docs/parser/PARSER_IMPROVEMENTS_CC.md +0 -425
  132. package/docs/parser/grammar_review_gemini.md +0 -116
  133. package/docs/participants-function.md +0 -25
  134. package/docs/responsive-participant-margin.md +0 -52
  135. package/docs/starter.md +0 -9
  136. package/docs/superpowers/plans/2026-03-27-e2e-test-reorg.md +0 -698
  137. package/docs/superpowers/plans/2026-03-30-emoji-support.md +0 -1220
  138. package/docs/superpowers/plans/2026-03-30-self-correcting-scoring.md +0 -206
  139. package/docs/superpowers/plans/2026-04-15-keyboard-editing-on-diagram.md +0 -1992
  140. package/docs/superpowers/plans/2026-04-15-zenuml-ux-research-skill.md +0 -1452
  141. package/docs/ux-research/.gitkeep +0 -0
  142. package/docs/ux-research/2026-04-15-rename-participant.md +0 -156
  143. package/docs/ux-research/2026-04-18-insert-participant.md +0 -151
  144. package/docs/width-translate-and-offsets.md +0 -62
  145. package/docs/xss.md +0 -59
  146. package/e2e/data/compare-cases.js +0 -1090
  147. package/e2e/data/diff-algorithm.js +0 -199
  148. package/e2e/fixtures/create-message.html +0 -26
  149. package/e2e/fixtures/editable-label.html +0 -35
  150. package/e2e/fixtures/editable-span.html +0 -122
  151. package/e2e/fixtures/empty-diagram.html +0 -23
  152. package/e2e/fixtures/fixture.html +0 -31
  153. package/e2e/fixtures/insert-participant.html +0 -23
  154. package/e2e/fixtures/reorder-cross-fragment.html +0 -31
  155. package/e2e/fixtures/reorder-fragment.html +0 -29
  156. package/e2e/fixtures/reorder-message.html +0 -27
  157. package/e2e/fixtures/svg-test.html +0 -21
  158. package/e2e/fixtures/type-switch.html +0 -29
  159. package/e2e/tools/canonical-history.html +0 -908
  160. package/e2e/tools/compare-case.html +0 -371
  161. package/e2e/tools/compare.html +0 -35
  162. package/e2e/tools/native-diff-ext/background.js +0 -60
  163. package/e2e/tools/native-diff-ext/bridge.js +0 -26
  164. package/e2e/tools/native-diff-ext/content.js +0 -194
  165. package/e2e/tools/svg-preview.html +0 -56
  166. package/embed.html +0 -193
  167. package/eslint.config.mjs +0 -35
  168. package/firebase-debug.log +0 -108
  169. package/iframe-container-demo/diagram.html +0 -124
  170. package/iframe-container-demo/host.html +0 -817
  171. package/index.html +0 -771
  172. package/mermaid-zenuml-async-spa-auth.png +0 -0
  173. package/mermaid-zenuml-async-spa-auth.snapshot.md +0 -96
  174. package/newsletter/unicode-support-announcement.md +0 -134
  175. package/playground/creation.html +0 -53
  176. package/playground/message.html +0 -63
  177. package/playwright.config.ts +0 -40
  178. package/renderer.html +0 -366
  179. package/scripts/analyze-compare-case/collect-data.mjs +0 -1134
  180. package/scripts/analyze-compare-case/config.mjs +0 -102
  181. package/scripts/analyze-compare-case/geometry.mjs +0 -101
  182. package/scripts/analyze-compare-case/native-diff.mjs +0 -224
  183. package/scripts/analyze-compare-case/output.mjs +0 -74
  184. package/scripts/analyze-compare-case/panel-diff.mjs +0 -114
  185. package/scripts/analyze-compare-case/report.mjs +0 -162
  186. package/scripts/analyze-compare-case/residual-scopes.mjs +0 -347
  187. package/scripts/analyze-compare-case/scoring.mjs +0 -829
  188. package/scripts/analyze-compare-case.mjs +0 -149
  189. package/scripts/bump-version.js +0 -117
  190. package/scripts/snapshot-dual.js +0 -173
  191. package/scripts/update-snapshots.js +0 -70
  192. package/skills/dia-scoring/SKILL.md +0 -129
  193. package/skills/dia-scoring/agents/openai.yaml +0 -7
  194. package/skills/dia-scoring/references/selectors-and-keys.md +0 -253
  195. package/tailwind.config.js +0 -126
  196. package/test-compression.html +0 -274
  197. package/test-mermaid-zenuml.html +0 -57
  198. package/test-setup.ts +0 -124
  199. package/test-url-params.html +0 -192
  200. package/tsconfig.app.json +0 -31
  201. package/tsconfig.node.json +0 -24
  202. package/tsconfig.test.json +0 -9
  203. package/vite.config.lib.ts +0 -93
  204. package/vite.config.ts +0 -84
  205. package/wrangler.toml +0 -18
File without changes
@@ -1,156 +0,0 @@
1
- ---
2
- scenario_id: rename-participant
3
- scenario_title: Rename a participant via keyboard
4
- run_date: 2026-04-15
5
- zenuml_core_sha: 2c6e120d
6
- audited_url: http://localhost:4000
7
- skill_version: 0.1.0
8
- gap_count: { high: 2, med: 1, low: 0 }
9
- ---
10
-
11
- # UX Research — Rename a participant via keyboard
12
-
13
- ## Executive summary
14
-
15
- Renaming a participant on the ZenUML canvas is not possible without editing the DSL text directly. Clicking a participant opens a style panel (color/type selector) instead of selecting it for editing. Pressing Enter toggles that style panel rather than entering inline edit mode. No inline edit affordance exists for participant labels — the code explicitly treats the participant as a "style" button (`role="button"`, `title="Click to style participant"`). The most important thing to fix is adding an inline edit mode triggered by Enter on a selected participant, separating selection from the style panel.
16
-
17
- ## Scenario recap
18
-
19
- **User intent:** The user has a participant `A` on the canvas and wants to rename it to `Alice` without leaving the keyboard.
20
-
21
- **Starting DSL:**
22
- ```
23
- A
24
- ```
25
-
26
- **Target DSL:**
27
- ```
28
- Alice
29
- ```
30
-
31
- ## Observed walkthrough
32
-
33
- ### Path 1 — Click to select, then Enter to edit (most discoverable keyboard path)
34
-
35
- 1. **Typed `A` in the DSL editor** to seed the starting state. Participant A appeared on the canvas in both DOM and SVG previews. (Setup, not walkthrough.)
36
-
37
- 2. **Clicked participant A on the canvas.** A style panel opened immediately below the participant showing COLOR swatches and TYPE selectors (Actor, Boundary, Control, Entity, Database, Queue, None). Participant A showed a blue selection ring (`ring-2 ring-sky-400`). The panel opened on click rather than on a secondary gesture — click is conflated with "open panel," not "select for editing."
38
-
39
- 3. **Pressed Enter.** The style panel closed. The blue selection ring remained on participant A. **No edit mode appeared** — the label `A` remained static text, not an editable input.
40
-
41
- 4. **Pressed Enter again.** The style panel re-opened. Enter toggles the style panel. The code at `Participant.tsx:114-118` explicitly routes `Enter` to `onSelect()` which opens the style panel. There is no code path from Enter to an inline edit mode.
42
-
43
- 5. **Pressed Escape.** The style panel closed and selection was cleared (blue ring disappeared). This is correct behavior — Escape dismisses the panel and deselects.
44
-
45
- ### Path 2 — Double-click to edit
46
-
47
- 6. **Double-clicked participant A on the canvas.** The style panel opened, identical to single-click. Double-click does not enter edit mode. There is no differentiation between single-click and double-click on the participant component.
48
-
49
- ### Path 3 — DSL editor fallback
50
-
51
- 7. The only way to rename participant A to Alice is to edit the DSL directly: click into the code editor, select `A`, type `Alice`. This works but requires leaving the canvas entirely and switching to the text editor pane.
52
-
53
- ## Gaps
54
-
55
- ### Gap 1 — Enter on selected participant toggles style panel instead of entering edit mode
56
- **Severity:** high
57
- **Catalog ID:** KBD-03 (Enter enters edit mode on selected item)
58
- **Observed:** After clicking participant A (which opens the style panel) and pressing Escape to close the panel, pressing Enter re-opens the style panel. The cycle repeats: Enter and Escape toggle the panel. No edit mode is ever entered.
59
- **Expected:** Pressing Enter on a selected participant should replace the label with a focused, editable input so the user can retype the name. The style panel should be accessed via a secondary gesture (e.g., right-click or a dedicated button), not via the same interaction as editing.
60
- **Exemplars:** Figma (Enter on selected text layer enters edit mode), Notion (Enter on selected cell enters edit mode), tldraw (Enter on selected shape enters text edit), VS Code (F2 on selected item enters rename mode).
61
- **Rationale:** The Enter key is the universal "act on this selection" key in canvas editors. Using it for the style panel instead of inline editing forces users to leave the canvas for what should be the most common operation — renaming.
62
- **Suggested fix:** `src/components/DiagramFrame/SeqDiagram/LifeLineLayer/Participant.tsx:114-118` — the `onKeyDown` handler currently routes `Enter` to `onSelect()` (which opens the style panel). This handler should be changed so that Enter triggers an inline edit mode instead. The `onSelect` / style-panel behavior should move to a secondary gesture. No existing edit-mode handler was found — this is a missing code path, not a misrouted one. The concurrent `keyboard-editing-on-diagram` design spec (`docs/superpowers/specs/2026-04-15-keyboard-editing-on-diagram-design.md`) appears to address exactly this gap.
63
-
64
- ### Gap 2 — No inline edit mode exists for participant labels
65
- **Severity:** high
66
- **Catalog ID:** EDT-01 (entering edit mode replaces the label with a focused, editable input)
67
- **Observed:** The participant component renders the label as a static `<div>` inside a flex container. There is no code path that transforms this into an editable input, contenteditable span, or any form of inline editing. The `role="button"` and `title="Click to style participant"` attributes on the participant div confirm this is intentionally a style-trigger, not an edit target.
68
- **Expected:** When edit mode is triggered (by Enter, double-click, or F2), the static label should be replaced by an editable input at the same visual position, with focus and cursor placement at end-of-text.
69
- **Exemplars:** Figma (text layers become editable on Enter), tldraw (shapes show text input on Enter), Notion (cells become editable inputs on Enter).
70
- **Rationale:** Inline editing is the single most expected interaction on a canvas item after selection. Its absence forces all renaming through the DSL editor, breaking the user's flow between "I see the diagram" and "I edit the diagram."
71
- **Suggested fix:** `src/components/DiagramFrame/SeqDiagram/LifeLineLayer/Participant.tsx` — no edit-mode logic exists in this file. A new state (`isEditing`) and a conditional render path are needed: when `isEditing` is true, render an `<input>` or `<EditableSpan>` (an existing component at `src/components/common/EditableSpan/EditableSpan.tsx`) in place of the static label. Note: `EditableSpan` already exists in the codebase and has keyboard handling — it may be reusable here.
72
-
73
- ### Gap 3 — Click conflates selection with opening the style panel
74
- **Severity:** med
75
- **Catalog ID:** novel — candidate for new rule (SEL category: "single click selects without opening secondary panels")
76
- **Observed:** Clicking participant A simultaneously selects it (blue ring) AND opens the style panel. There is no state where the participant is "selected but no panel is open" that is reachable via mouse click alone. The only way to reach a "selected, no panel" state is to click (panel opens) then press Escape (panel closes, but selection clears too per `ParticipantStylePanel.tsx:103-104`).
77
- **Expected:** Single click should select the participant with a visible selection indicator. The style panel should open on a secondary gesture: either Enter (but we'd want Enter for edit mode), right-click, or a dedicated "style" button/icon on the selection frame. Selection and style-editing should be separate states.
78
- **Exemplars:** Figma (click selects; panel is opened via sidebar or right-click), tldraw (click selects; style panel is in a persistent sidebar), draw.io (click selects; style is in a panel that opens on secondary action).
79
- **Rationale:** Conflating selection with panel-opening means the user can never "just select" a participant to then decide what to do with it (rename, delete, move, style). Every click forces the style panel into view, which is the wrong action most of the time.
80
- **Suggested fix:** `src/components/DiagramFrame/SeqDiagram/LifeLineLayer/Participant.tsx:113` — the `onClick` handler should set selection state WITHOUT opening the style panel. The style panel trigger should move to a different gesture.
81
-
82
- ## Coverage
83
-
84
- Tested hypotheses (no gap found):
85
- - KBD-05: Escape on the style panel correctly closes it (verified in `ParticipantStylePanel.tsx:101-105`)
86
- - SEL-03: Selection indicator (blue ring) IS implemented via `ring-2 ring-sky-400` CSS class (verified at `Participant.tsx:102`)
87
- - Participant IS keyboard-focusable via `tabIndex={0}` (verified at `Participant.tsx:120`)
88
- - KBD-04: Escape during edit mode cancels — not testable because edit mode does not exist
89
-
90
- Not tested (out of scope for this scenario):
91
- - INS, UND, FBK categories
92
- - Arrow-key navigation between participants (this scenario focuses on a single participant)
93
-
94
- Skipped (couldn't form a testable hypothesis):
95
- - KBD-01: Tab into the diagram widget — the Dev Workbench layout (editor + two previews) makes tab-order analysis unreliable in this context; would need a standalone embed to test properly
96
-
97
- ## Best-practice sources
98
-
99
- **Bundled catalog IDs referenced:**
100
- - KBD-03 (Enter enters edit mode)
101
- - KBD-04 (Escape cancels edit)
102
- - KBD-05 (Escape on selected item deselects or exits)
103
- - EDT-01 (inline edit replaces label with input)
104
- - EDT-02 (caret at end of text)
105
- - SEL-01 (click selects exclusively)
106
- - SEL-03 (visible selection indicator)
107
-
108
- **Web sources fetched during this run:**
109
- - None needed — catalog coverage was sufficient for this scenario.
110
-
111
- ## Playwright regression snippet
112
-
113
- Paste into `zenuml-core/tests/ux/rename-participant.spec.ts` once the gaps are fixed. The skill emits this snippet; it does not run it.
114
-
115
- ```typescript
116
- import { test, expect } from "@playwright/test";
117
-
118
- test("KBD-03: Enter on selected participant enters edit mode", async ({ page }) => {
119
- await page.goto("/");
120
- // Clear and seed DSL with a single participant
121
- await page.click("text=Clear");
122
- await page.locator(".cm-content").click();
123
- await page.keyboard.type("A");
124
- // Wait for participant to render
125
- await page.waitForSelector('[data-participant-id="A"]');
126
- // Click participant to select it
127
- await page.click('[data-participant-id="A"]');
128
- // Close the style panel if it opens
129
- await page.keyboard.press("Escape");
130
- // Now press Enter — should enter edit mode
131
- await page.click('[data-participant-id="A"]');
132
- await page.keyboard.press("Enter");
133
- // Verify an editable input appeared
134
- const input = page.locator('[data-participant-id="A"] input, [data-participant-id="A"] [contenteditable]');
135
- await expect(input).toBeFocused({ timeout: 500 });
136
- });
137
-
138
- test("EDT-01: Edit mode shows editable input at label position", async ({ page }) => {
139
- await page.goto("/");
140
- await page.click("text=Clear");
141
- await page.locator(".cm-content").click();
142
- await page.keyboard.type("A");
143
- await page.waitForSelector('[data-participant-id="A"]');
144
- // Enter edit mode (once implemented)
145
- await page.click('[data-participant-id="A"]');
146
- await page.keyboard.press("Enter");
147
- const input = page.locator('[data-participant-id="A"] input, [data-participant-id="A"] [contenteditable]');
148
- await expect(input).toBeVisible();
149
- // Type new name and commit
150
- await input.fill("Alice");
151
- await page.keyboard.press("Enter");
152
- // Verify DSL updated
153
- const editor = page.locator(".cm-content");
154
- await expect(editor).toContainText("Alice");
155
- });
156
- ```
@@ -1,151 +0,0 @@
1
- ---
2
- scenario_id: insert-participant
3
- scenario_title: Insert a participant on a blank diagram
4
- run_date: 2026-04-18
5
- zenuml_core_sha: e8db2a7a
6
- audited_url: http://localhost:4000
7
- skill_version: 0.1.0
8
- gap_count: { high: 3, med: 1, low: 0 }
9
- ---
10
-
11
- # UX Research — Insert a participant on a blank diagram
12
-
13
- ## Executive summary
14
-
15
- Inserting a participant on a blank ZenUML canvas requires editing the DSL text directly — there is no canvas-based insertion affordance. Clicking the canvas background, double-clicking, right-clicking, and pressing Enter or other keys on the canvas all produce no response. The diagram container has no `tabindex`, `role`, or ARIA attributes, so it cannot receive keyboard focus at all. The only working path is clicking into the DSL editor pane and typing the participant name. While this works, it means ZenUML's canvas is purely a rendering surface with no creation capabilities — a significant gap for new users who expect a diagramming tool to let them create directly on the canvas.
16
-
17
- ## Scenario recap
18
-
19
- **User intent:** The user opens ZenUML to a blank diagram and wants to add one participant named `Alice` so they can start modelling.
20
-
21
- **Starting DSL:**
22
- ```
23
- ```
24
-
25
- **Target DSL:**
26
- ```
27
- Alice
28
- ```
29
-
30
- ## Observed walkthrough
31
-
32
- 1. **Cleared editor** — clicked "Clear" button to reach blank-diagram state. Canvas shows a single anonymous participant (person icon) with no label.
33
- 2. **Clicked canvas background** (empty area to the right of the default participant) — no visible change, no context menu, no insertion affordance.
34
- 3. **Searched for "Add participant" or "+" button** using accessibility tree scan — no matching elements found. Bottom toolbar contains only info, emoji/settings, version badge, zoom controls, and ZenUML.com link.
35
- 4. **Pressed Enter on canvas** — no response. Canvas does not appear to receive keyboard focus.
36
- 5. **Right-clicked canvas background** — no custom context menu appeared (browser default was suppressed but nothing replaced it).
37
- 6. **Double-clicked canvas background** — no response.
38
- 7. **Fell back to DSL editor** — clicked into the code editor pane on the left, typed `Alice`. Canvas immediately rendered a participant labeled "Alice". Target state reached.
39
-
40
- ## Gaps
41
-
42
- ### Gap 1 — No canvas-based insertion affordance for participants
43
-
44
- **Severity:** high
45
- **Catalog ID:** INS-01
46
- **Observed:** The only way to insert a participant is by typing directly into the DSL editor pane. No button, context menu, toolbar item, or click gesture on the canvas creates a new participant.
47
- **Expected:** At least one insertion path that does not require opening or touching the DSL editor directly (e.g., a visible "+" button, a right-click context menu, or clicking empty canvas space to create a participant).
48
- **Exemplars:** tldraw (visible toolbar with shape tools), Figma (toolbar + keyboard shortcuts), Miro (visible "+" for stickies), draw.io (drag from shape palette).
49
- **Rationale:** For a tool growing canvas affordances, the canvas should be a primary creation surface. Being forced into the DSL editor to create the most basic element is a high-friction barrier for new users.
50
- **Suggested fix:** No code path found — this is a missing implementation, not a misrouted one. The canvas (`src/components/DiagramFrame/SeqDiagram/`) has no insertion handlers. Participant creation is exclusively driven by DSL parsing in `src/parser/ToCollector.js` and `src/parser/Participants.ts:110` (`Add` method).
51
-
52
- ### Gap 2 — No keyboard-only insertion path
53
-
54
- **Severity:** high
55
- **Catalog ID:** INS-03
56
- **Observed:** Pressing Enter, Tab, or letter keys while the canvas area has apparent focus produces no effect. There is no keyboard shortcut to insert a participant.
57
- **Expected:** At least one keyboard-only path from blank state to new participant (e.g., pressing `p` to create a participant, or Tab into the canvas then Enter to create).
58
- **Exemplars:** Miro (Tab from one sticky creates a sibling), Linear (Cmd+Enter for new issue), Notion (Enter in a database for a new row).
59
- **Rationale:** ZenUML's user base is text-first and keyboard-native. A mouse-only workflow (click into DSL editor) is acceptable but a keyboard shortcut for the most common creation action would match user expectations.
60
- **Suggested fix:** No keyboard event handlers exist on the diagram container (`src/components/DiagramFrame/SeqDiagram/SeqDiagram.tsx`). The container div has no `tabindex` attribute, so it cannot receive focus or keyboard events. Only `src/components/common/EditableSpan/EditableSpan.tsx:111` and `src/components/DiagramFrame/Debug/index.tsx:8` have keydown handlers.
61
-
62
- ### Gap 3 — Diagram container is not a focusable widget
63
-
64
- **Severity:** high
65
- **Catalog ID:** KBD-01
66
- **Observed:** The diagram container (`div.zenuml.sequence-diagram`) has no `role`, no `tabindex`, and no `aria-label`. It cannot receive keyboard focus via Tab. Pressing Tab from the editor pane skips over the diagram entirely.
67
- **Expected:** The diagram should be one Tab stop from the page's perspective (the ARIA composite-widget convention). Tab moves focus into the diagram; Shift+Tab moves focus out.
68
- **Exemplars:** Figma canvas (focusable, receives keyboard events), VS Code tree view (composite widget with `role="tree"` and `tabindex`).
69
- **Rationale:** Without being focusable, the diagram cannot support any keyboard interaction — selection, navigation, editing, or insertion. This is the foundational gap that blocks KBD-02 through KBD-06.
70
- **Suggested fix:** The diagram container is rendered in `src/components/DiagramFrame/SeqDiagram/SeqDiagram.tsx`. It needs `tabIndex={0}`, `role="application"` or `role="group"`, and `aria-label="Sequence diagram"`. No such attributes exist anywhere in the component tree — grep for `tabindex` and `role=` across `src/components/` returned zero matches.
71
-
72
- ### Gap 4 — Insertion affordance not discoverable within 10 seconds
73
-
74
- **Severity:** med
75
- **Catalog ID:** INS-02
76
- **Observed:** On a blank canvas, a new user sees a single anonymous participant icon and an empty area. No visual cue suggests how to create a new participant. The DSL editor on the left has a dark background with just a line number "1" — it does not prompt or hint that typing there creates participants. Discovery required switching mental models from "canvas tool" to "text editor" and realizing the left pane is the creation mechanism.
77
- **Expected:** The primary insertion affordance should be discoverable by a new user within ~10 seconds of looking at the canvas (e.g., a visible "Add participant" button, a "+" icon, or placeholder text like "Type a participant name here").
78
- **Exemplars:** tldraw (visible toolbar with labeled shape tools), Figma (prominent toolbar), draw.io (shape palette on the left).
79
- **Rationale:** Discoverability is the gateway to every other interaction. Hidden affordances turn new users away.
80
- **Suggested fix:** The blank-canvas state is rendered by `src/components/DiagramFrame/SeqDiagram/SeqDiagram.tsx` and `src/components/DiagramFrame/SeqDiagram/LifeLineLayer/`. No empty-state guidance, placeholder text, or onboarding affordance exists. A `TipsDialog` component exists at `src/components/DiagramFrame/Tutorial/TipsDialog.tsx` but it is a general tips dialog, not contextual insertion guidance.
81
-
82
- ## Coverage
83
-
84
- Tested hypotheses (no gap found):
85
- - DSL editor correctly creates a participant when text is typed (verified — `Alice` appeared immediately on canvas)
86
- - No console errors during walkthrough (verified — only debug-level render messages)
87
-
88
- Not tested (out of scope for this scenario):
89
- - EDT-02 (caret placement on edit entry — tested in rename-participant scenario)
90
- - EDT-04 (commit/cancel contract — tested in rename-participant scenario)
91
- - KBD-04, KBD-05 (Escape behavior — requires selection, which requires focus, which is blocked by Gap 3)
92
- - UND-01, UND-02, UND-03 (undo/redo — tested in undo-insert scenario)
93
-
94
- Skipped (couldn't form a testable hypothesis):
95
- - FOC-01 (focus after keyboard insertion — no keyboard insertion path exists, so this is untestable; subsumed by Gap 2)
96
- - KBD-02 (arrow-key navigation — diagram not focusable; subsumed by Gap 3)
97
- - KBD-06 (Tab to spawn sibling — diagram not focusable; subsumed by Gap 3)
98
-
99
- ## Best-practice sources
100
-
101
- **Bundled catalog IDs referenced:**
102
- INS-01, INS-02, INS-03, KBD-01, FOC-01, EDT-03
103
-
104
- **Web sources fetched during this run:**
105
- None — catalog and common sense were sufficient for this scenario.
106
-
107
- ## Playwright regression snippet
108
-
109
- Paste into `zenuml-core/tests/ux/insert-participant.spec.ts` once the gap is fixed. The skill emits this snippet; it does not run it.
110
-
111
- ```typescript
112
- import { test, expect } from "@playwright/test";
113
-
114
- test.describe("insert-participant UX", () => {
115
- test.beforeEach(async ({ page }) => {
116
- await page.goto("http://localhost:4000");
117
- // Clear to blank state
118
- await page.getByRole("button", { name: "Clear" }).click();
119
- });
120
-
121
- test("diagram container is focusable via Tab", async ({ page }) => {
122
- // Gap 3: KBD-01
123
- const diagram = page.locator(".sequence-diagram");
124
- await expect(diagram).toHaveAttribute("tabindex", "0");
125
- await page.keyboard.press("Tab");
126
- // After enough Tabs, focus should land on the diagram
127
- await expect(diagram).toBeFocused();
128
- });
129
-
130
- test("canvas has a visible insertion affordance", async ({ page }) => {
131
- // Gap 1: INS-01 + Gap 4: INS-02
132
- // Look for a button or affordance that creates a participant
133
- const addButton = page.getByRole("button", { name: /add|insert|new|participant|\+/i });
134
- await expect(addButton).toBeVisible();
135
- });
136
-
137
- test("keyboard shortcut inserts a participant", async ({ page }) => {
138
- // Gap 2: INS-03
139
- const diagram = page.locator(".sequence-diagram");
140
- await diagram.focus();
141
- // Press the expected shortcut (adjust once implemented)
142
- await page.keyboard.press("p");
143
- // A new participant should appear in edit mode
144
- const editInput = diagram.locator("input, [contenteditable=true]");
145
- await expect(editInput).toBeFocused();
146
- await page.keyboard.type("Alice");
147
- await page.keyboard.press("Enter");
148
- await expect(diagram.locator(".participant")).toContainText("Alice");
149
- });
150
- });
151
- ```
@@ -1,62 +0,0 @@
1
- # Interaction width
2
-
3
- Interaction width, in the most simple scenario, is defined by the distance of two participants -
4
- `from` and `to`.
5
-
6
- ## Simple case
7
-
8
- ### Width
9
-
10
- In the following
11
- Each '◻' is a pixel
12
-
13
- ```
14
- | A | | B |
15
- 1 2 3 4 5 6 7 8 9 a b c d e f g h
16
- ◻ ◻ ◻ ◻ ◻ ◻ ◻ ◻ ◻ ◻ ◻ ◻ ◻ ◻ ◻ ◻ ◻
17
- ```
18
-
19
- #### 1. Interaction width
20
-
21
- Interaction will overlap the left lifeline but not the right.
22
- A has left as 2, center as 4 and right as 6; B has a/c/e. For Interaction `m2` in (A.m1{B.m2}),
23
- the width should be 4 to c (inclusive) that is 9 (`c - 4 + 1`). This is `distance(from, to)`.
24
-
25
- #### 2. Message width
26
-
27
- Message width should be 100% content + interactionBorderWidthx2 - ((OccurrenceWidth - 1)/2)x2 - interactionBorderWidth.
28
-
29
- ### Left
30
-
31
- #### 1. Message left
32
-
33
- ```
34
- 100% // content width of interaction
35
- + InteractionBorderWidth x 2
36
- - ((OccurrenceWidth-1)/2) x 2
37
- ```
38
-
39
- #### 2. Self Occurrence Left
40
-
41
- ```
42
- left: width of InteractionBorderWidth
43
- ```
44
-
45
- ### Offset
46
-
47
- There are a few ways to implement offset, we have to combine them.
48
-
49
- #### 1. Padding of occurance
50
-
51
- > Suppose the width of an occurance is 5 (border width 1x2, content 3)
52
-
53
- To aligh Occurance's center, we need to set its left. An occurance
54
- at `left: 100%` will be from c to g. Note that the 100% only consider
55
- the content width.
56
-
57
- To align its center to c, we
58
- have to move back by 3 (`(occuranceWidth-1)/2 - interactionBorderWidth - LifelineWidth`).
59
-
60
- occurance must have a padding of 1 that is (width - boarder x 2 - 1) / 2.
61
-
62
- ## Self call indent
package/docs/xss.md DELETED
@@ -1,59 +0,0 @@
1
- In this document we are disclosing a cross-site scripting vulnerability in the
2
- [@zenuml/core](https://www.npmjs.com/package/@zenuml/core) package.
3
- XSS is a type of security vulnerability that allows an attacker to inject
4
- malicious code into a web page viewed by other users.
5
-
6
- # How to reproduce
7
-
8
- ZenUML generates sequence diagrams from text. If the text contains a
9
- malicious script, it will be executed when the diagram is rendered.
10
-
11
- The following content is known to pop an alert box:
12
-
13
- ```
14
- "><img src=x onerror=alert(1)>ent #FFEBE6
15
- ```
16
-
17
- # Known affected products
18
-
19
- | Product | Severity | Comment |
20
- |-------------------|----------|---------------------------------------------------------------------|
21
- | Confluence plugin | P3 | Content must be create by registered users; scripts run in sandbox. |
22
- | Web App | P3 | Only the current user can view content created by themselves. |
23
- | Chrome Extension | P3 | Only the current user can view content created by themselves. |
24
- | Desktop (Win/Mac) | P3 | Only the current user can view content created by themselves. |
25
- | JetBrains plugin | P3 | Scripts run inside sandbox. |
26
-
27
- # What is the cause of the vulnerability?
28
-
29
- The cause of the XSS vulnerability in @zenuml/core is due to accepting an arbitrary text as `innerHTML` in the
30
- WidthProviderFunc.ts file. Specifically, the vulnerability is located in the code that measures the width
31
- of the message or participant element in the library. The issue is present in line 29 of the file, which
32
- can be found at [this URL](https://github.com/ZenUml/core/blob/577f2a550a0b82a392215875298bc358a8feff0d/src/positioning/WidthProviderFunc.ts#L29).
33
- In this line, the code uses unsanitized user input which is passed as an argument in the function, this
34
- allows an attacker to inject malicious JavaScript code into the web page viewed potentially by other users.
35
-
36
- ```
37
- let hiddenDiv = document.querySelector('.textarea-hidden-div') as HTMLDivElement;
38
- if (!hiddenDiv) {
39
- const newDiv = document.createElement('div');
40
- ...
41
- document.body.appendChild(newDiv);
42
- hiddenDiv = newDiv;
43
- }
44
- hiddenDiv.innerHTML = text;
45
- const scrollWidth = hiddenDiv.scrollWidth;
46
- return scrollWidth;
47
- ```
48
-
49
- # Fix of the vulnerability
50
-
51
- The fix is to replace the `innerHTML` with `textContent` in the code. This will prevent the browser from
52
- interpreting the text as HTML and will treat it as plain text.
53
-
54
- ```
55
- ...
56
- hiddenDiv.textContent = text;
57
- const scrollWidth = hiddenDiv.scrollWidth;
58
- return scrollWidth;
59
- ```