@zenuml/core 3.47.9 → 3.48.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.
- package/dist/cloud-icons-eHuugVSv.js.map +1 -0
- package/dist/zenuml.esm.mjs +2153 -2156
- package/dist/zenuml.esm.mjs.map +1 -0
- package/dist/zenuml.js +82 -82
- package/dist/zenuml.js.map +1 -0
- package/package.json +11 -1
- package/src/cli/zenuml.ts +1164 -0
- package/.agents/skills/babysit-pr/SKILL.md +0 -223
- package/.agents/skills/babysit-pr/agents/openai.yaml +0 -7
- package/.agents/skills/dia-scoring/SKILL.md +0 -139
- package/.agents/skills/dia-scoring/agents/openai.yaml +0 -7
- package/.agents/skills/dia-scoring/references/selectors-and-keys.md +0 -253
- package/.agents/skills/land-pr/SKILL.md +0 -120
- package/.agents/skills/propagate-core-release/SKILL.md +0 -205
- package/.agents/skills/propagate-core-release/agents/openai.yaml +0 -7
- package/.agents/skills/propagate-core-release/references/downstreams.md +0 -42
- package/.agents/skills/ship-branch/SKILL.md +0 -105
- package/.agents/skills/submit-branch/SKILL.md +0 -76
- package/.agents/skills/validate-branch/SKILL.md +0 -72
- package/.claude/commands/README.md +0 -162
- package/.claude/commands/analyze.md +0 -101
- package/.claude/commands/clarify.md +0 -158
- package/.claude/commands/code-review.md +0 -322
- package/.claude/commands/constitution.md +0 -73
- package/.claude/commands/create-docs.md +0 -309
- package/.claude/commands/full-context.md +0 -121
- package/.claude/commands/gemini-consult.md +0 -164
- package/.claude/commands/handoff.md +0 -146
- package/.claude/commands/implement.md +0 -56
- package/.claude/commands/plan.md +0 -43
- package/.claude/commands/refactor.md +0 -188
- package/.claude/commands/specify.md +0 -21
- package/.claude/commands/tasks.md +0 -62
- package/.claude/commands/update-docs.md +0 -314
- package/.claude/hooks/README.md +0 -270
- package/.claude/hooks/config/sensitive-patterns.json +0 -86
- package/.claude/hooks/gemini-context-injector.sh +0 -129
- package/.claude/hooks/mcp-security-scan.sh +0 -147
- package/.claude/hooks/notify.sh +0 -103
- package/.claude/hooks/setup/hook-setup.md +0 -96
- package/.claude/hooks/setup/settings.json.template +0 -63
- package/.claude/hooks/sounds/complete.wav +0 -0
- package/.claude/hooks/sounds/input-needed.wav +0 -0
- package/.claude/hooks/subagent-context-injector.sh +0 -65
- package/.claude/skills/babysit-pr/SKILL.md +0 -223
- package/.claude/skills/babysit-pr/agents/openai.yaml +0 -7
- package/.claude/skills/dia-scoring/SKILL.md +0 -139
- package/.claude/skills/dia-scoring/agents/openai.yaml +0 -7
- package/.claude/skills/dia-scoring/references/selectors-and-keys.md +0 -253
- package/.claude/skills/emoji-eval/SKILL.md +0 -187
- package/.claude/skills/land-pr/SKILL.md +0 -120
- package/.claude/skills/propagate-core-release/SKILL.md +0 -205
- package/.claude/skills/propagate-core-release/agents/openai.yaml +0 -7
- package/.claude/skills/propagate-core-release/references/downstreams.md +0 -42
- package/.claude/skills/ship-branch/SKILL.md +0 -105
- package/.claude/skills/submit-branch/SKILL.md +0 -76
- package/.claude/skills/validate-branch/SKILL.md +0 -72
- package/.claude/skills/zenuml-ux-research/SKILL.md +0 -183
- package/.claude/skills/zenuml-ux-research/references/assertion-catalog.md +0 -261
- package/.claude/skills/zenuml-ux-research/references/best-practices-overview.md +0 -56
- package/.claude/skills/zenuml-ux-research/references/report-template.md +0 -89
- package/.claude/skills/zenuml-ux-research/references/scenarios/edit-message-label.md +0 -37
- package/.claude/skills/zenuml-ux-research/references/scenarios/insert-message.md +0 -36
- package/.claude/skills/zenuml-ux-research/references/scenarios/insert-participant.md +0 -31
- package/.claude/skills/zenuml-ux-research/references/scenarios/rename-participant.md +0 -33
- package/.claude/skills/zenuml-ux-research/references/scenarios/undo-insert.md +0 -35
- package/.devcontainer/devcontainer.json +0 -21
- package/.dockerignore +0 -19
- package/.eslintrc.js +0 -39
- package/.git-blame-ignore-revs +0 -6
- package/.kiro/hooks/README.md +0 -38
- package/.kiro/hooks/session-sound-notification.js +0 -44
- package/.kiro/hooks/session-sound-notification.json +0 -23
- package/.mcp.json.example +0 -17
- package/.nvmrc +0 -1
- package/.prettierignore +0 -4
- package/.prettierrc +0 -1
- package/.specify/memory/constitution.md +0 -33
- package/.specify/scripts/bash/check-prerequisites.sh +0 -166
- package/.specify/scripts/bash/common.sh +0 -113
- package/.specify/scripts/bash/create-new-feature.sh +0 -97
- package/.specify/scripts/bash/setup-plan.sh +0 -60
- package/.specify/scripts/bash/update-agent-context.sh +0 -728
- package/.specify/templates/agent-file-template.md +0 -23
- package/.specify/templates/plan-template.md +0 -219
- package/.specify/templates/spec-template.md +0 -116
- package/.specify/templates/tasks-template.md +0 -127
- package/.storybook/main.ts +0 -25
- package/.storybook/preview.ts +0 -29
- package/.watchmanconfig +0 -3
- package/AGENTS.md +0 -26
- package/CLAUDE.md +0 -124
- package/DEPLOYMENT.md +0 -62
- package/Dockerfile +0 -36
- package/IMPLEMENTATION_PLAN.md +0 -163
- package/Integration/vanilla-js/index.html +0 -42
- package/MCP-ASSISTANT-RULES.md +0 -85
- package/README_CN.md +0 -15
- package/TUTORIAL.md +0 -116
- package/antlr/antlr-4.11.1-complete.jar +0 -0
- package/bun.lock +0 -1544
- package/bunfig.toml +0 -52
- package/docs/UNICODE_SUPPORT.md +0 -179
- package/docs/ai-context/deployment-infrastructure.md +0 -21
- package/docs/ai-context/docs-overview.md +0 -89
- package/docs/ai-context/handoff.md +0 -174
- package/docs/ai-context/project-structure.md +0 -160
- package/docs/ai-context/system-integration.md +0 -21
- package/docs/asciidoc/contributor.adoc +0 -54
- package/docs/asciidoc/create-my-own-theme.adoc +0 -149
- package/docs/asciidoc/images/creation-component.png +0 -0
- package/docs/asciidoc/images/creation-rtl.png +0 -0
- package/docs/asciidoc/images/message-arrow-rtl.png +0 -0
- package/docs/asciidoc/images/occurrence.png +0 -0
- package/docs/asciidoc/images/return-message-conflict.png +0 -0
- package/docs/asciidoc/images/shift-up-half-the-height.png +0 -0
- package/docs/asciidoc/images/three-layer-info-arch.png +0 -0
- package/docs/asciidoc/images/vertical-alignment.svg +0 -1
- package/docs/asciidoc/images/vertically-aligning.png +0 -0
- package/docs/asciidoc/index.adoc +0 -277
- package/docs/asciidoc/theme-debug-web-app.png +0 -0
- package/docs/asciidoc/tutorial.adoc +0 -22
- package/docs/asciidoc/user-css.png +0 -0
- package/docs/async-vs-sync-parser-rules.md +0 -81
- package/docs/divider-parser-allow-spaces.md +0 -38
- package/docs/highlighting-messages.md +0 -52
- package/docs/images/editor-sample.png +0 -0
- package/docs/inherited-vs-provided-from.md +0 -64
- package/docs/parser/Assignment.md +0 -8
- package/docs/parser/PARSER_IMPROVEMENTS_CC.md +0 -425
- package/docs/parser/grammar_review_gemini.md +0 -116
- package/docs/participants-function.md +0 -25
- package/docs/responsive-participant-margin.md +0 -52
- package/docs/starter.md +0 -9
- package/docs/superpowers/plans/2026-03-27-e2e-test-reorg.md +0 -698
- package/docs/superpowers/plans/2026-03-30-emoji-support.md +0 -1220
- package/docs/superpowers/plans/2026-03-30-self-correcting-scoring.md +0 -206
- package/docs/superpowers/plans/2026-04-15-keyboard-editing-on-diagram.md +0 -1992
- package/docs/superpowers/plans/2026-04-15-zenuml-ux-research-skill.md +0 -1452
- package/docs/ux-research/.gitkeep +0 -0
- package/docs/ux-research/2026-04-15-rename-participant.md +0 -156
- package/docs/ux-research/2026-04-18-insert-participant.md +0 -151
- package/docs/width-translate-and-offsets.md +0 -62
- package/docs/xss.md +0 -59
- package/e2e/data/compare-cases.js +0 -1090
- package/e2e/data/diff-algorithm.js +0 -199
- package/e2e/fixtures/create-message.html +0 -26
- package/e2e/fixtures/editable-label.html +0 -35
- package/e2e/fixtures/editable-span.html +0 -122
- package/e2e/fixtures/empty-diagram.html +0 -23
- package/e2e/fixtures/fixture.html +0 -31
- package/e2e/fixtures/insert-participant.html +0 -23
- package/e2e/fixtures/reorder-cross-fragment.html +0 -31
- package/e2e/fixtures/reorder-fragment.html +0 -29
- package/e2e/fixtures/reorder-message.html +0 -27
- package/e2e/fixtures/svg-test.html +0 -21
- package/e2e/fixtures/type-switch.html +0 -29
- package/e2e/tools/canonical-history.html +0 -908
- package/e2e/tools/compare-case.html +0 -371
- package/e2e/tools/compare.html +0 -35
- package/e2e/tools/native-diff-ext/background.js +0 -60
- package/e2e/tools/native-diff-ext/bridge.js +0 -26
- package/e2e/tools/native-diff-ext/content.js +0 -194
- package/e2e/tools/svg-preview.html +0 -56
- package/embed.html +0 -193
- package/eslint.config.mjs +0 -35
- package/firebase-debug.log +0 -108
- package/iframe-container-demo/diagram.html +0 -124
- package/iframe-container-demo/host.html +0 -817
- package/index.html +0 -771
- package/mermaid-zenuml-async-spa-auth.png +0 -0
- package/mermaid-zenuml-async-spa-auth.snapshot.md +0 -96
- package/newsletter/unicode-support-announcement.md +0 -134
- package/playground/creation.html +0 -53
- package/playground/message.html +0 -63
- package/playwright.config.ts +0 -40
- package/renderer.html +0 -366
- package/scripts/analyze-compare-case/collect-data.mjs +0 -1134
- package/scripts/analyze-compare-case/config.mjs +0 -102
- package/scripts/analyze-compare-case/geometry.mjs +0 -101
- package/scripts/analyze-compare-case/native-diff.mjs +0 -224
- package/scripts/analyze-compare-case/output.mjs +0 -74
- package/scripts/analyze-compare-case/panel-diff.mjs +0 -114
- package/scripts/analyze-compare-case/report.mjs +0 -162
- package/scripts/analyze-compare-case/residual-scopes.mjs +0 -347
- package/scripts/analyze-compare-case/scoring.mjs +0 -829
- package/scripts/analyze-compare-case.mjs +0 -149
- package/scripts/bump-version.js +0 -117
- package/scripts/snapshot-dual.js +0 -173
- package/scripts/update-snapshots.js +0 -70
- package/skills/dia-scoring/SKILL.md +0 -129
- package/skills/dia-scoring/agents/openai.yaml +0 -7
- package/skills/dia-scoring/references/selectors-and-keys.md +0 -253
- package/tailwind.config.js +0 -126
- package/test-compression.html +0 -274
- package/test-mermaid-zenuml.html +0 -57
- package/test-setup.ts +0 -124
- package/test-url-params.html +0 -192
- package/tsconfig.app.json +0 -31
- package/tsconfig.node.json +0 -24
- package/tsconfig.test.json +0 -9
- package/vite.config.lib.ts +0 -93
- package/vite.config.ts +0 -84
- 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
|
-
```
|