Skip to content

feat(editor): inline editing with formatting toolbar, canvas UX fixes, and dashboard cross-tab sync - #126

Merged
peter6055 merged 23 commits into
mainfrom
feat/resolve-inline-editing-2-feature-improvement
Aug 7, 2026
Merged

peter6055 merged 23 commits into
mainfrom
feat/resolve-inline-editing-2-feature-improvement

Conversation

@peter6055

@peter6055 peter6055 commented Aug 6, 2026 •

Copy link
Copy Markdown
Owner

Summary

Transforms the inline text editor from a plain <textarea> into a WYSIWYG contentEditable editor with a formatting toolbar (bold, italic, left/center/right alignment) that round-trips rich HTML back into Mermaid diagram code. Also overhauls canvas geometry/interaction handling so the editor matches the rendered graph (Mermaid foreignObject text layout, vertical centering, safe double-click selection, comment-mode copy cursor), extracts the sequence participant toolbar into a server-safe client module, adds cross-tab Dashboard file-list sync, persists telemetry preferences, and introduces random-port test-server + Playwright global-setup infrastructure with unit/e2e tests.

Net diff: ~2258 insertions / ~641 deletions across 29 files.


Changes

1. WYSIWYG inline editor with rich text

  • InlineTextEditor.tsx — Replaced <textarea> with <div contentEditable>; rich-text entry via innerHTML; typography/layout tuned to match Mermaid's flowchart output (fontSize: 16, lineHeight: 1.5, Mermaid font stack, whiteSpace: break-spaces, flex + vertical centering). Added exported getActiveFormats() helper for bold/italic/align detection. Keyboard shortcuts: Ctrl/Cmd+B, Ctrl/Cmd+I, Ctrl/Cmd+A (Range-based select-all). Improved click-outside/blur handling that respects the toolbar and defers submission.
  • LiveMaidEditor.tsx — handleFormatText rewritten as DOM-level formatting: wraps/unwraps <b>/<i> on the selection, alignment via a top-level <div style="text-align:..."> wrapper, color via <span style="color:...">. Save flow reads latest text directly from the DOM, normalizes via normalizeHtmlForMermaid, and treats no-op edits as silent closes. Flowchart node-label save now prefers a dedicated NODE["..."] quoted-bracket regex (fixes labels containing parens).
  • EditorCanvas.tsx — Threaded handleFormatText through; [data-inline-editor]/[data-class-text-editor] added to pointer-down and double-click floating-UI guards.

2. Inline editor formatting toolbar

  • Floating toolbar above the editor (Bold / Italic / divider / Align Left / Center / Right) with selection save/restore, active-state highlighting, and zoom compensation (scale(var(--zoom-inverse-scale, ...))).

3. Inline editor UX fixes

  • useCanvasInteraction.ts — textBox now derives from the full node rect so the editor covers the rendered node and wraps lines like Mermaid; ref.select() replaced with Range.selectNodeContents; double-clicks inside an active editor are ignored by the canvas; HTML preserved for flowchart ["..."] shapes; comment mode now places comment anchors on shapes/sequence bands via a new onShapeCommentPlace callback (refactor of the composer into openShapeCommentComposerRef).
  • EditorCanvas.tsx — Comment mode forces copy cursor over diagram SVG elements and suppresses selection/hover/resize/note overlays.
  • ClassTextEditor.tsx — Added data-inline-editor + double-click guard; font bumped to 16px to match.

4. Sequence diagram toolbar extraction

  • sequence.tsx slimmed to just the plugin definition (server-safe, no "use client"); new SequenceToolbar.tsx hosts the participant type picker (8 archetypes, auto-ID, right-side insertion). ParticipantIcon/PARTICIPANT_TYPES re-exported for compatibility.

5. Dashboard cross-tab sync

  • New dashboardSync.ts — BroadcastChannel("livemaid-dashboard-sync") with a localStorage fallback; Dashboard refetches silently on tab visibility change and cross-tab broadcasts; diagram creation broadcasts a refresh to other tabs.

6. Testing infrastructure

  • scripts/start-test-server.mjs, get-random-port.mjs, playwright-global-setup.mjs — random 20000–30000 port, unique temp distDir, no project-lock conflicts for concurrent agents.
  • playwright.config.ts — fixed-port webServer replaced with globalSetup; baseURL from PLAYWRIGHT_BASE_URL.
  • next.config.ts — distDir reads NEXT_TEST_DIST_DIR; allowedDevOrigins defaults to [] unless ALLOWED_DEV_ORIGINS set.
  • Docs: AGENTS.md random-port workflow, new reference/standards/browser-mcp.md, updated reference/standards/testing.md.

7. New tests

  • html-utils.test.ts — htmlToPlainText, containsHtml, normalizeHtmlForMermaid.
  • inline-text-editor.test.tsx — getActiveFormats bold/italic/align across caret, partial, and full selections.
  • dashboard-cross-tab-sync.spec.ts — Playwright e2e for tab-visibility and broadcast refresh.

8. Telemetry persistence

  • telemetry/index.ts — new STORAGE_KEY; initTelemetry restores persisted usage/debug toggles from localStorage (was hardcoded OFF).
  • telemetryProvider.tsx — unified storage key (livemaid:telemetry), bootstraps instance from saved prefs in one pass.

9. Utilities & misc

  • utils.ts — new htmlToPlainText, containsHtml, normalizeHtmlForMermaid used by all inline-edit save paths.
  • Removed stray smoke-test-1-editor.png; tmp/ ignored/excluded from lint & TS.

Behavior / risk notes

  • inlineInputRef type changed HTMLTextAreaElement → HTMLDivElement (all in-repo call sites migrated).
  • Inline editor is now HTML-based; rich labels are written as HTML into Mermaid source, normalized on save. No-op edits silently close.
  • Flowchart label-save regex priority changed (quoted-bracket shapes matched first).
  • Telemetry storage key renamed (livemaid:telemetry-preferences → livemaid:telemetry) — previously saved prefs are orphaned once.
  • Dashboard initial-load skeleton now only applies to first fetch; silent refreshes skip the 600 ms delay.
  • Dev-origin allow-list default removed (next.config.ts); environments without ALLOWED_DEV_ORIGINS lose implicit localhost/tailnet allowances (mitigated here by .env).

Verification

  • npm run prepush passes (typecheck, prettier, Vitest: 158 tests across 10 files, production build).
  • ESLint: 101 pre-existing warnings, 0 errors.
  • Manual browser checks of inline editing (centering, typing, commit-on-blur).

Summary by CodeRabbit

  • New Features
    • Format inline diagram text with bold, italic, alignment, and color controls.
    • Add sequence-diagram participants using eight participant types with automatic labels.
    • Place comments directly on flowchart shapes and sequence messages.
    • Keep dashboard listings synchronized across browser tabs and refresh when returning to the tab.
  • Bug Fixes
    • Improved preservation and sanitization of formatted diagram content.
    • Prevented editing and dragging conflicts around inline editors and comment interactions.
    • Telemetry preferences now persist and load correctly.

peter6055 added 15 commits July 28, 2026 13:21
- Replace textarea with contentEditable div for inline text editing
- Preserve HTML formatting (bold, italic, alignment) when editing nodes
- Add formatting toolbar with Bold, Italic, and Alignment buttons
- Support text-align style (left, center, right) in node labels
- Add keyboard shortcuts (Ctrl+B for bold, Ctrl+I for italic)
- Fix toolbar click-outside and blur event handling
- Properly scale editor and toolbar with zoom level
- Add utility functions: htmlToPlainText, containsHtml, normalizeHtmlForMermaid
- Add unit tests for HTML utility functions
The contentEditable div adds visual line breaks at word-boundary characters
(/, -, .) when text wraps. These were being saved back to the source code,
causing extra line breaks in the diagram.

Now removes <br> tags that appear immediately after or before these characters
since they are browser-added visual wraps, not intentional line breaks.
- Toolbar buttons now show active state for bold/italic/alignment
- Toggle support: clicking bold on bold text removes bold
- Track selection changes to update toolbar state in real-time
- Skip initial input event after contentEditable load (prevents browser auto-format)
- Compare original content on submit - skip save if no real changes
- Read allowedDevOrigins from .env instead of hardcoding
- Remove formatting toolbar (bold/italic/alignment buttons) from inline editor
- Keep contentEditable to render formatted text (bold, alignment) visually
- Block browser formatting shortcuts (Ctrl+B/I/U) - users edit styling in code panel
- Keep nowrap to prevent browser-added line wraps during editing
… telemetry

Restore the inline text editor formatting toolbar (bold, italic, alignment)
with refined UX: per-diagram-type default alignment, partial-selection bold,
and Ctrl+B/Ctrl+I/Ctrl+Enter shortcuts. Add telemetry events for toolbar
usage and persist preferences in localStorage. Include unit tests.

Fix sequence diagram creation: the sequence plugin was the only diagram
plugin carrying a "use client" directive and React hooks, causing it to be
dropped from the server-side catalog and breaking POST /api/diagrams with
"Unsupported diagram type". Move the stateful toolbar to a client component
(SequenceToolbar) and keep the plugin module server-safe.
…mat toggle

- Tighten inline text editor toolbar positioning so it hugs the input box (4 px visual gap) instead of floating far above.

- When toggling bold/italic/color on the whole text, keep the alignment wrapper at the top level so the active alignment state remains visible in the toolbar.
…al setup

- test:dev picks a random free port in 20000-30000 and uses a unique temporary
  distDir under tmp/ so multiple agents can run test servers concurrently
- add scripts/start-test-server.mjs, scripts/get-random-port.mjs,
  scripts/playwright-global-setup.mjs (starts server, sets PLAYWRIGHT_BASE_URL)
- playwright.config.ts uses the global setup instead of a fixed 3435 webServer
- eslint.config.mjs ignores tmp/ and tsconfig excludes it (generated artifacts)
- document why in-app Browser MCP can hang and when to switch to Playwright:
  reference/standards/browser-mcp.md; update testing.md and AGENTS.md
  dev-server rules
When a diagram is created in the editor, notify the Dashboard via
BroadcastChannel (with localStorage fallback) so open Dashboard tabs
silently refetch the file list on visibilitychange and cross-tab sync,
without interrupting the editor flow.
@coderabbitai

coderabbitai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@peter6055, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 11 seconds

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 53ae4b8c-9305-4d4a-bfc3-7b9b5ef99003

📥 Commits

Reviewing files that changed from the base of the PR and between 8b1eb75 and 176a979.

📒 Files selected for processing (6)
  • package.json
  • scripts/playwright-global-setup.mjs
  • src/components/editor/LiveMaidEditor.tsx
  • src/lib/dashboardSync.ts
  • src/lib/utils.ts
  • tsconfig.json
📝 Walkthrough

Walkthrough

The PR adds isolated browser test-server startup, content-editable Mermaid editing, sequence participant insertion, dashboard refresh synchronization, shape comments, HTML sanitization, and telemetry preference persistence.

Changes

Test infrastructure isolation

Layer / File(s) Summary
Randomized test-server lifecycle
scripts/get-random-port.mjs, scripts/playwright-global-setup.mjs, scripts/start-test-server.mjs, next.config.ts, package.json, playwright.config.ts
Test servers use random ports, temporary distDir values, readiness detection, route warm-up, and signal-based teardown.
Browser testing and workspace configuration
AGENTS.md, reference/standards/*, .gitignore, eslint.config.mjs, tsconfig.json
Testing guidance prioritizes Playwright and isolated servers. Temporary server artifacts are excluded from repository tooling.

Mermaid editor workflows

Layer / File(s) Summary
Content-editable inline editing
src/components/editor/InlineTextEditor.tsx, src/lib/utils.ts, src/lib/formatting-helpers.ts, src/test/html-*.test.ts*, src/test/inline-text-editor.test.tsx
Inline editing uses sanitized HTML, selection-aware formatting, alignment controls, and Mermaid normalization. Unit tests cover formatting and sanitization.
Canvas comments and edit integration
src/components/editor/EditorCanvas.tsx, src/components/editor/LiveMaidEditor.tsx, src/hooks/useCanvasInteraction.ts, src/components/editor/ClassTextEditor.tsx, src/test/comment-mode.spec.ts
Canvas interactions protect editor overlays, route shape comments with geometry, suppress conflicting comment-mode controls, and save current normalized DOM content.
Sequence participant toolbar
src/components/editor/SequenceToolbar.tsx, src/lib/diagrams/sequence.tsx, src/test/sequence-*
The sequence toolbar inserts unique typed participants and preserves shared participant exports. Unit and browser tests cover insertion and rendering.

Dashboard refresh synchronization

Layer / File(s) Summary
Cross-tab refresh transport and dashboard integration
src/lib/dashboardSync.ts, src/components/Dashboard.tsx, src/components/editor/LiveMaidEditor.tsx, src/test/dashboard-cross-tab-sync.spec.ts
Dashboard refresh messages use BroadcastChannel with a localStorage fallback. Dashboard data refreshes on visibility changes, diagram creation, and cross-tab notifications. Tests cover both refresh paths.

Telemetry preference initialization

Layer / File(s) Summary
Shared telemetry preference resolution
src/lib/telemetry/index.ts, src/lib/telemetry/telemetryProvider.tsx, src/test/telemetry-persistence.test.ts
Telemetry uses a shared storage key, restores valid preferences, applies explicit overrides, and persists provider updates.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DashboardTabA
  participant dashboardSync
  participant BroadcastChannel
  participant DashboardTabB
  participant DashboardAPI
  DashboardTabA->>dashboardSync: broadcastDashboardRefresh()
  dashboardSync->>BroadcastChannel: publish refresh message
  BroadcastChannel->>DashboardTabB: deliver refresh message
  DashboardTabB->>dashboardSync: invoke refresh callback
  DashboardTabB->>DashboardAPI: fetch diagrams and folders
  DashboardAPI-->>DashboardTabB: return updated data
Loading

Possibly related PRs

Poem

A rabbit edits Mermaid lines,
With bold and italics in tidy designs.
Ports hop freely, tabs refresh,
Safe HTML keeps markup fresh.
New actors join the sequence stage,
Telemetry remembers each setting’s page.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary inline editing, canvas UX, and dashboard synchronization changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/resolve-inline-editing-2-feature-improvement

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread src/components/editor/InlineTextEditor.tsx Fixed
Comment thread src/lib/utils.ts Fixed
Comment thread src/lib/utils.ts Fixed
Comment thread src/lib/utils.ts Fixed
@peter6055 peter6055 changed the title feat(editor): vertically center inline editor text feat(editor): WYSIWYG inline editing with formatting toolbar, canvas UX fixes, and dashboard cross-tab sync Aug 6, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/components/editor/LiveMaidEditor.tsx (1)

3216-3231: 🗄️ Data Integrity & Integration | 🔴 Critical | 🏗️ Heavy lift

The flowchart and block paths save raw editor HTML without normalization.

The sequence branches call normalizeHtmlForMermaid (lines 3083, 3137, 3154, 3188). These branches do not.

The content-editable editor emits a <div> wrapper for each Enter press; it never produces \n. Three consequences follow.

Line 3231: latestEditingText.replace(/\n/g, "<br/>") is now a no-op, because no \n exists in the input. A two-line flowchart node label is written to the Mermaid source as line1<div>line2</div> instead of line1<br/>line2.

Line 3216: currentLabel !== latestEditingText compares a plain-text label parsed from the source against raw editor HTML. The two rarely match, so the "skip when unchanged" guard almost always reports a change and forces a recompile.

Line 3174: the block-label path applies only \n collapsing, so <b> and <div> markup from the editor is written into the loop/alt keyword line.

Use normalizedText (line 3003) in all three places, consistent with the sequence branches.

Verify the saved Mermaid source for a multi-line flowchart node label, an edge label, and a sequence block label, following the implement, browser-test, screenshot, evaluate, fix, and iterate loop. As per coding guidelines "For UI features, rendering logic, or complex client-side changes, follow the required implement, browser-test, screenshot, evaluate, fix, and iterate loop; address every error discovered during testing."

Also applies to: 3174-3174

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/editor/LiveMaidEditor.tsx` around lines 3216 - 3231, Use the
existing normalizedText value in the flowchart node, edge-label, and block-label
save paths instead of raw latestEditingText or newline-only replacement. Update
the unchanged-label comparison near currentLabel and the cluster rename value
near editingTextForSave, plus the block-label handling around the loop/alt path,
so editor div markup becomes Mermaid-compatible line breaks and formatting is
not written into source.

Source: Coding guidelines

🟡 Minor comments (12)
src/lib/dashboardSync.ts-19-35 (1)

19-35: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Send one refresh notification per request.

Lines 20-35 send a BroadcastChannel message and a storage event when both APIs work. onDashboardRefresh subscribes to both, so each Dashboard tab can run its callback twice.

Return after a successful BroadcastChannel send. Use localStorage only when that send is unavailable or fails.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/dashboardSync.ts` around lines 19 - 35, Ensure the dashboard refresh
notification is sent only once per request: in the BroadcastChannel block,
return immediately after a successful postMessage and close operation. Keep the
localStorage fallback in the surrounding flow for cases where BroadcastChannel
is unavailable or throws, preserving onDashboardRefresh compatibility.
src/test/dashboard-cross-tab-sync.spec.ts-139-152 (1)

139-152: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Wait for the initial dashboard data before changing the mock response.

The heading is visible before fetchData completes. The initial request can read updatedDiagrams after Line 152, so this test can pass without a BroadcastChannel refresh.

Wait for Alpha Flow to be visible before assigning updatedDiagrams.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/dashboard-cross-tab-sync.spec.ts` around lines 139 - 152, In the
dashboard cross-tab sync test, update the initial-load assertions after
navigating `otherPage` to wait for the `Alpha Flow` diagram to be visible before
assigning `updatedDiagrams`. Use a specific diagram-card assertion rather than
the heading visibility check, ensuring the initial fetch completes before the
mock response changes.
AGENTS.md-78-80 (1)

78-80: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Label the Markdown code fence.

The fence at Line 78 triggers markdownlint MD040. Use text or console as the fence language.

Suggested fix
-```
+```text
Starting test dev server on http://localhost:<port>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@AGENTS.md` around lines 78 - 80, Label the Markdown code fence containing
“Starting test dev server on http://localhost:<port>” with an appropriate
language such as text or console to satisfy markdownlint MD040.

Source: Linters/SAST tools

src/components/editor/InlineTextEditor.tsx-487-487 (1)

487-487: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The editor colors are hard-coded and ignore the theme prop.

bg-white, color: "#1c1c21", and the fixed font stack render a light editor over every Mermaid theme. On the dark and redux themes the overlay does not match the node it replaces, which contradicts the documented requirement that inline editors inherit the underlying text typography and appearance.

The component receives theme but does not read it. Derive the background and text color from theme, or read the computed style of the element being edited.

Also applies to: 546-548

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/editor/InlineTextEditor.tsx` at line 487, Update the inline
editor styling in InlineTextEditor to use the active theme instead of hard-coded
white background, dark text, and fixed typography. Read the relevant background,
text color, and font properties from theme or the edited element’s computed
style, and apply them to the editor so dark and redux overlays match the node
being replaced.
src/components/editor/LiveMaidEditor.tsx-2861-2886 (1)

2861-2886: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

querySelector("div[style*='text-align']") can match a nested div.

querySelector returns the first matching descendant in document order at any depth. Content pasted into the editor, or content produced by wrapEntireContent at lines 2838-2843, can contain an inner div carrying text-align. Toggling alignment then mutates that inner div instead of the top-level wrapper, and the alignment applies to only part of the label.

Scope the lookup to a direct child, for example el.querySelector(":scope > div[style*='text-align']"). Apply the same change at line 2838.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/editor/LiveMaidEditor.tsx` around lines 2861 - 2886, Update
the alignment wrapper lookups in the format-handling logic and wrapEntireContent
to only match a direct child of el, not nested descendants. Replace the broad
div[style*='text-align'] queries with a scoped direct-child selector so
alignment always targets the top-level wrapper.
src/lib/utils.ts-16-27 (1)

16-27: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Decode &amp; last to avoid double-unescaping.

The current order decodes &amp; first. An input such as &amp;lt; becomes &lt; and then <, which changes the user text. Move the &amp; replacement after the other entity replacements.

🐛 Proposed fix for entity decode order
     .replace(/<[^>]+>/g, "") // Strip all other tags
-    .replace(/&amp;/g, "&") // Decode entities
-    .replace(/&lt;/g, "<")
+    .replace(/&lt;/g, "<") // Decode entities
     .replace(/&gt;/g, ">")
     .replace(/&quot;/g, '"')
     .replace(/&`#39`;/g, "'")
     .replace(/&nbsp;/g, " ")
+    .replace(/&amp;/g, "&")
     .replace(/\n{3,}/g, "\n\n") // Collapse multiple newlines
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/utils.ts` around lines 16 - 27, In the HTML entity decoding chain,
move the `&amp;` replacement to after the `&lt;`, `&gt;`, `&quot;`, `&`#39`;`, and
`&nbsp;` replacements so encoded entities such as `&amp;lt;` remain literal
`&lt;` text. Leave the surrounding tag stripping and whitespace normalization
unchanged.

Source: Linters/SAST tools

src/hooks/useCanvasInteraction.ts-3672-3678 (1)

3672-3678: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Commit an in-progress inline edit before routing to the comment composer.

This early return runs before the isInlineEditing handling at lines 3714-3739. The comment-mode toolbar button in src/components/editor/LiveMaidEditor.tsx (line 4849) stays enabled while the inline editor is open. If the user enables comment mode during an edit and then clicks a shape, this branch returns immediately: commitEditRef.current?.() never runs, the typed text is discarded, and isInlineEditing stays true while the comment composer opens.

🐛 Proposed fix
       // Comment mode takes priority over selection: clicking a shape while adding a
       // comment should attach the comment to that shape, not select it.
       if (isCommentMode && clicked?.cleanId && onShapeCommentPlace) {
+        if (isInlineEditing) {
+          commitEditRef.current?.();
+          setIsInlineEditing(false);
+        }
         onShapeCommentPlace(clicked.cleanId, clicked.newSelectionBox);
         return;
       }

Apply the same guard to the message-band branch at lines 3757-3761.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useCanvasInteraction.ts` around lines 3672 - 3678, Update the
comment-mode shape-click branch in the interaction handler to invoke
commitEditRef.current?.() before onShapeCommentPlace and its early return,
ensuring inline edits are committed and editing state is cleared before opening
the comment composer. Apply the same commit guard to the message-band
comment-mode branch as well.
src/components/editor/LiveMaidEditor.tsx-3005-3014 (1)

3005-3014: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

This no-change path clears the selection; the existing one keeps it.

Lines 3355-3358 handle the same outcome (nothing changed) and deliberately keep the selection. Their comment states that clearing forces an SVG recompile that shifts node positions and makes the next click miss the node.

The new early return performs the opposite action: it clears selectedNodeId, selectedSvgId, selectionBox, and textBox. The user now loses the selection after opening an editor and closing it without edits.

Align this branch with the documented behaviour.

🐛 Proposed fix
     if (normalizedText === originalNormalized) {
       setIsInlineEditing(false);
-      setSelectedNodeId(null);
-      setSelectedSvgId(null);
-      setSelectionBox(null);
-      setTextBox(null);
       return;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/editor/LiveMaidEditor.tsx` around lines 3005 - 3014, Update
the no-change early-return branch in the inline editor flow around
normalizeHtmlForMermaid to close editing without clearing the current selection
state. Remove the selectedNodeId, selectedSvgId, selectionBox, and textBox
resets, matching the existing unchanged-content handling near the corresponding
no-change path.
src/components/editor/SequenceToolbar.tsx-162-170 (1)

162-170: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

The fallback identifier can collide with an existing participant.

P${Date.now().toString().slice(-3)} produces one of 1000 values and is never checked against usedIds. A collision makes Mermaid treat the new declaration as the existing participant, so the two merge into one lifeline and the user sees no new column.

Continue the search instead of guessing.

🐛 Proposed fix
     let newId = "";
     for (let i = 0; i < 26; i += 1) {
       const c = String.fromCharCode(65 + i);
       if (!usedIds.has(c)) {
         newId = c;
         break;
       }
     }
-    if (!newId) newId = `P${Date.now().toString().slice(-3)}`;
+    for (let i = 1; !newId; i += 1) {
+      const candidate = `P${i}`;
+      if (!usedIds.has(candidate)) newId = candidate;
+    }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/editor/SequenceToolbar.tsx` around lines 162 - 170, Update the
identifier-generation logic near the newId search so every fallback candidate is
checked against usedIds before being selected. Continue generating candidates
until an unused identifier is found, and preserve the existing A–Z preference
while ensuring the returned identifier cannot collide with an existing
participant.
src/components/editor/LiveMaidEditor.tsx-3305-3322 (1)

3305-3322: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

$ sequences in the label corrupt the replacement.

String.prototype.replace interprets $&, $1, $`, and $' inside the replacement string. editingTextForSave comes from user text, so a label such as Cost $1 per unit inserts the first capture group instead of the literal $1.

Use a replacer function so the value is inserted verbatim.

🐛 Proposed fix
-          newCode = newCode.replace(quoteBracketGlobal, `$1$2${editingTextForSave}$4`);
+          newCode = newCode.replace(
+            quoteBracketGlobal,
+            (_m, p1, p2, _p3, p4) => `${p1}${p2}${editingTextForSave}${p4}`,
+          );

Apply the same change at line 3315 and to the standalone-node replacement at lines 3319-3322.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/editor/LiveMaidEditor.tsx` around lines 3305 - 3322, Update
the replacement calls in the quoteBracket, nodeRegex, and standaloneRegex
branches to use replacer functions instead of replacement strings, ensuring
editingTextForSave is inserted verbatim and user-provided $ sequences are not
interpreted as replacement tokens.
src/test/inline-text-editor.test.tsx-8-12 (1)

8-12: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the editor element after each test.

beforeEach appends a new div to document.body and nothing removes it. Every test leaves one behind. Assertions still pass because each test holds its own el, but any later test that queries document sees the stale editors, and the leaked selection state carries across tests.

💚 Proposed fix
-import { describe, it, expect, beforeEach } from "vitest";
+import { describe, it, expect, beforeEach, afterEach } from "vitest";
 import { getActiveFormats } from "`@/components/editor/InlineTextEditor`";
 
 describe("InlineTextEditor getActiveFormats", () => {
   let el: HTMLDivElement;
 
   beforeEach(() => {
     el = document.createElement("div");
     el.contentEditable = "true";
     document.body.appendChild(el);
   });
+
+  afterEach(() => {
+    window.getSelection()?.removeAllRanges();
+    el.remove();
+  });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/inline-text-editor.test.tsx` around lines 8 - 12, Update the test
cleanup around the beforeEach setup for el by adding an afterEach hook that
removes the created editor element from document.body and clears the test
reference as appropriate. Ensure each test leaves no editor or selection-related
DOM state behind.
reference/features/editor/overview.md-27-29 (1)

27-29: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

The documented display: table does not match the implementation.

src/components/editor/InlineTextEditor.tsx sets display: "flex" with alignItems: "center" and justifyContent: "center" (lines 549-551). The PR description also states that the editor now uses a flex layout with centered alignment. No display: table appears in the component.

Update this bullet so the reference matches the shipped behaviour.

📝 Proposed correction
-- Flowchart inline editors mirror Mermaid's `foreignObject` text layout: `display: table`,
-  `white-space: break-spaces`, and the same font stack so line breaks match what Mermaid will
-  render on commit.
+- Flowchart inline editors mirror Mermaid's `foreignObject` text layout: a centered flex box
+  (`display: flex`, `align-items: center`, `justify-content: center`), `white-space: break-spaces`,
+  and the same font stack so line breaks match what Mermaid will render on commit.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@reference/features/editor/overview.md` around lines 27 - 29, Update the
Flowchart inline editors bullet in the reference to describe the implemented
flex layout with centered alignment, matching InlineTextEditor rather than
documenting display: table. Retain the existing font and whitespace behavior
only if it remains accurate.
🧹 Nitpick comments (13)
src/components/editor/InlineTextEditor.tsx (2)

119-172: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Collapse the three duplicated format-detection blocks into one helper.

The bold and italic checks repeat the same ancestor walk four times. Extract one helper and call it per format.

♻️ Proposed refactor
+  const allFormatted = (nodes: Text[], fmt: "bold" | "italic") =>
+    nodes.every((textNode) => {
+      let node: Node | null = textNode.parentNode;
+      while (node && node !== el) {
+        if (isFormatTag(node, fmt)) return true;
+        node = node.parentNode;
+      }
+      return false;
+    });
+
   let bold = false;
   let italic = false;
 
   if (!range || !el.contains(range.commonAncestorContainer)) {
     // No valid selection inside the editor: check the whole editor content.
     bold = isAllContentFormatted(el, "bold");
     italic = isAllContentFormatted(el, "italic");
   } else if (range.collapsed || coversAllContent(range)) {
     const textNodes = getTextNodesInRange(range, el);
     if (textNodes.length === 0) {
       bold = isAllContentFormatted(el, "bold");
       italic = isAllContentFormatted(el, "italic");
     } else {
-      bold = textNodes.every((textNode) => { /* … */ });
-      italic = textNodes.every((textNode) => { /* … */ });
+      bold = allFormatted(textNodes, "bold");
+      italic = allFormatted(textNodes, "italic");
     }
   } else {
     const textNodes = getTextNodesInRange(range, el).filter((n) => n.textContent?.trim());
-    if (textNodes.length === 0) {
-      bold = false;
-      italic = false;
-    } else {
-      bold = textNodes.every((textNode) => { /* … */ });
-      italic = textNodes.every((textNode) => { /* … */ });
-    }
+    bold = textNodes.length > 0 && allFormatted(textNodes, "bold");
+    italic = textNodes.length > 0 && allFormatted(textNodes, "italic");
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/editor/InlineTextEditor.tsx` around lines 119 - 172, Extract
the repeated ancestor-walk logic from the selection handling into a single
format-detection helper, parameterized by the format name such as “bold” or
“italic.” Use that helper for both format checks across the invalid-selection,
collapsed/full-content, and partial-selection branches while preserving the
existing empty-selection fallback behavior.

377-474: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract one toolbar button component and expose the toggle state.

The five buttons repeat the same onMouseDown/onClick body. They also expose no pressed state: the active style is visual only, so assistive technology cannot report whether bold, italic, or an alignment is active.

Extract a local FormatButton that takes format, label, icon, and active, and add aria-pressed={active} and aria-label to it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/editor/InlineTextEditor.tsx` around lines 377 - 474, The five
toolbar buttons in the editor repeat identical event handling and lack
accessibility state. Extract a local FormatButton component accepting format,
label, icon, and active; move the shared mouse and click behavior into it, set
aria-pressed from active, and provide aria-label from label, then replace the
bold, italic, and alignment button markup while preserving their existing
formats, icons, titles, and active styling.
src/test/inline-text-editor.test.tsx (1)

31-123: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add a case for the no-selection branch.

getActiveFormats has a distinct path when no range exists inside the editor: it falls back to isAllContentFormatted over the whole content (lines 122-125 of InlineTextEditor.tsx). The toolbar hits that path after the editor loses focus, and no test covers it.

Add a test that calls window.getSelection()?.removeAllRanges() and then asserts getActiveFormats(el) for fully bold content.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/inline-text-editor.test.tsx` around lines 31 - 123, Add a test
covering the no-selection path in getActiveFormats: set the editor content to
fully bold text, clear all ranges with window.getSelection()?.removeAllRanges(),
then assert the returned formats indicate bold true, italic false, and an empty
alignment. Keep the existing selection-based tests unchanged.
src/hooks/useCanvasInteraction.ts (3)

1544-1554: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated select-all block into a helper.

The same focus-then-select-all sequence appears four times in this file: lines 1544-1554, 1809-1819, 3246-3256, and 3602-3612. Extract one helper and call it from each site.

♻️ Proposed helper
// Module scope, next to the other DOM helpers.
function focusAndSelectAll(el: HTMLElement | null) {
  if (!el) return;
  el.focus();
  const range = document.createRange();
  range.selectNodeContents(el);
  const sel = window.getSelection();
  sel?.removeAllRanges();
  sel?.addRange(range);
}
         setTimeout(() => {
-          if (inlineInputRef.current) {
-            inlineInputRef.current.focus();
-            // Select all content in contentEditable div
-            const range = document.createRange();
-            range.selectNodeContents(inlineInputRef.current);
-            const sel = window.getSelection();
-            sel?.removeAllRanges();
-            sel?.addRange(range);
-          }
+          focusAndSelectAll(inlineInputRef.current);
         }, 10);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useCanvasInteraction.ts` around lines 1544 - 1554, Extract the
repeated focus-and-select-all logic into a module-scope helper named
focusAndSelectAll near the other DOM helpers, accepting an HTMLElement or null
and safely returning when absent. Replace the four duplicated blocks in the
interaction flows with calls to this helper while preserving their existing
timeout behavior.

3548-3595: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

This edge-label lookup duplicates the block at lines 3436-3479.

The same sequence runs earlier in handleEditClick: resolve g.edgePaths, index the clicked path, index into g.edgeLabels, and read foreignObject div, foreignObject span. Extract one helper, for example resolveEdgeLabelText(container, rawSvgId), and call it from both sites.

The comment at lines 3550-3552 is also incorrect. It states "label index = path index / 2", but allPaths already excludes .flowchart-link-hit-target, so the code uses indexOf directly with no division.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useCanvasInteraction.ts` around lines 3548 - 3595, Extract the
duplicated edge-label resolution logic from handleEditClick and the current
fallback block into a shared helper such as resolveEdgeLabelText(container,
rawSvgId), then use it at both call sites while preserving the existing text
extraction behavior. Update the nearby comment to remove the incorrect “path
index / 2” statement and describe direct indexing of the filtered edge paths.

9-9: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the unused containsHtml import.

containsHtml is imported in src/hooks/useCanvasInteraction.ts but has no references in this file, so remove the import to avoid littering the module with dead dependencies.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hooks/useCanvasInteraction.ts` at line 9, Remove the unused containsHtml
import from useCanvasInteraction.ts, leaving the remaining imports and hook
implementation unchanged.
src/components/editor/EditorCanvas.tsx (2)

2465-2499: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Replace the 25 selectors with one descendant rule.

Every selector in this block applies the same declaration. A single rule covers all of them, including future Mermaid element classes that this list does not enumerate.

The block is also global, not scoped to this canvas instance, so it applies to any other .mermaid-container on the page.

♻️ Proposed simplification
                 {isCommentMode && (
                   <style>{`
-                        .mermaid-container,
-                        .mermaid-container .react-transform-wrapper,
-                        /* … 23 more selectors … */
-                        .mermaid-container svg .default {
+                        .mermaid-container,
+                        .mermaid-container * {
                             cursor: copy !important;
                         }
                      `}</style>
                 )}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/editor/EditorCanvas.tsx` around lines 2465 - 2499, Replace the
enumerated selectors in the isCommentMode style block with one descendant rule
targeting .mermaid-container and all of its descendants, using the existing
cursor: copy !important declaration. Keep the rule within the comment-mode
conditional while covering future Mermaid elements without listing individual
classes.

808-815: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

[data-class-text-editor] is redundant next to [data-inline-editor].

ClassTextEditor sets both attributes on the same wrapper element (src/components/editor/ClassTextEditor.tsx, lines 94-95). Any element matched by [data-class-text-editor] is therefore already matched by [data-inline-editor]. The extra selector adds cost to a hit-test that runs on every document mousedown.

Keep [data-class-text-editor] only where class-editor-specific behaviour is needed, and drop it from the generic floating-UI guards.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/editor/EditorCanvas.tsx` around lines 808 - 815, Remove the
redundant [data-class-text-editor] selector from the generic floating-UI guard
in the hitFloatingUi check within EditorCanvas, leaving [data-inline-editor] to
cover the shared wrapper. Retain [data-class-text-editor] only in guards or
logic that requires class-editor-specific behavior.
src/components/editor/SequenceToolbar.tsx (1)

112-122: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive the display name from PARTICIPANT_TYPES.

displayNames repeats every label already declared in PARTICIPANT_TYPES at lines 96-105. A new participant type must be added in both places, and the two lists can drift.

♻️ Proposed refactor
   const handleAddParticipant = (type: string) => {
-    const displayNames: Record<string, string> = {
-      participant: "Participant",
-      actor: "Actor",
-      boundary: "Boundary",
-      control: "Control",
-      entity: "Entity",
-      database: "Database",
-      collections: "Collections",
-      queue: "Queue",
-    };
+    const displayName = PARTICIPANT_TYPES.find((t) => t.key === type)?.label ?? type;

Then use const baseLabel = `New ${displayName}`; at line 173.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/editor/SequenceToolbar.tsx` around lines 112 - 122, Update
handleAddParticipant to derive displayName from the existing PARTICIPANT_TYPES
definitions instead of maintaining the duplicate displayNames map. Reuse the
resolved label when constructing the “New” participant name, including the
existing fallback behavior if needed, and remove the redundant label list.
src/components/editor/LiveMaidEditor.tsx (2)

3083-3083: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Reuse the normalizedText computed at line 3003.

normalizeHtmlForMermaid(latestEditingText) is a pure call with the same argument in all four branches, and the result is already stored in normalizedText at line 3003. Replace each const newText = normalizeHtmlForMermaid(latestEditingText); with const newText = normalizedText;, or use normalizedText directly.

Also applies to: 3137-3137, 3154-3154, 3188-3188

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/editor/LiveMaidEditor.tsx` at line 3083, In the LiveMaidEditor
branch logic, replace each repeated normalizeHtmlForMermaid(latestEditingText)
call with the existing normalizedText value computed earlier, including all four
referenced branches. Preserve the current newText usage and avoid recomputing
the pure normalization.

2291-2297: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Simplify the repeated sequenceMessageIndex validity checks.

Line 2295 combines Number.isFinite(sequenceMessageIndex ?? Number.NaN) with an explicit null check, and line 2316 repeats the same pair in the opposite order. parseInt returns NaN for a malformed id, so one Number.isFinite guard is enough.

♻️ Proposed simplification
-      const sequenceMessageIndex = nodeId.startsWith("SEQ_MSG_")
-        ? parseInt(nodeId.replace("SEQ_MSG_", ""), 10)
-        : null;
-      const sequenceGeometry =
-        Number.isFinite(sequenceMessageIndex ?? Number.NaN) && sequenceMessageIndex !== null
-          ? getSequenceMessageEndpointGeometry(sequenceMessageIndex)
-          : null;
+      const parsedIndex = nodeId.startsWith("SEQ_MSG_")
+        ? parseInt(nodeId.replace("SEQ_MSG_", ""), 10)
+        : Number.NaN;
+      const sequenceMessageIndex = Number.isFinite(parsedIndex) ? parsedIndex : null;
+      const sequenceGeometry =
+        sequenceMessageIndex !== null
+          ? getSequenceMessageEndpointGeometry(sequenceMessageIndex)
+          : null;

Line 2316 then reduces to if (sequenceMessageIndex !== null) {.

Also applies to: 2316-2316

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/editor/LiveMaidEditor.tsx` around lines 2291 - 2297, In the
sequence message handling around sequenceMessageIndex, replace the combined
finite-and-null checks with a single Number.isFinite(sequenceMessageIndex)
guard, since malformed IDs produce NaN and null is not finite. Apply the same
simplification to the conditional at the later endpoint-processing branch,
preserving the existing geometry lookup and handling.
src/test/html-utils.test.ts (1)

38-45: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Add cases for double-unescaping and nested empty tags.

Two behaviours of the implementation are untested and both are currently wrong.

htmlToPlainText decodes &amp; first, so &amp;lt; becomes < instead of &lt;. Add expect(htmlToPlainText("&amp;lt;")).toBe("&lt;").

normalizeHtmlForMermaid removes empty formatting tags in a single non-recursive pass, so <b><i></i></b> leaves <b></b>. Add a case that pins the intended result.

Also applies to: 137-139

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/html-utils.test.ts` around lines 38 - 45, Extend the tests for
htmlToPlainText and normalizeHtmlForMermaid to cover the missing edge cases:
assert that “&amp;lt;” remains “&lt;” after decoding, and assert that nested
empty formatting tags such as “<b><i></i></b>” normalize to the intended empty
result without residual outer tags.
src/lib/telemetry/telemetryProvider.tsx (1)

39-39: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Keep the state updaters pure.

saveTelemetryPreferences writes to localStorage, but setUsageAnalytics and setDebugReporting call it inside functional state updaters at Lines 73-87. React may replay an updater, so the storage write can run more than once.

Move persistence to an effect that depends on usageAnalytics and debugReporting.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/telemetry/telemetryProvider.tsx` at line 39, Move the localStorage
write out of saveTelemetryPreferences calls inside the functional
setUsageAnalytics and setDebugReporting updaters. Add an effect in the telemetry
provider that persists the current usageAnalytics and debugReporting values
after state changes, while keeping the state updaters pure.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@AGENTS.md`:
- Around line 92-96: Update the startup logic around the PORT extraction and
readiness loop to poll tmux output for a bounded period until a localhost port
is available before constructing the readiness URL. If no port is extracted
within that limit, fail clearly instead of entering the existing curl loop;
preserve the current readiness polling once PORT is valid and ensure retries
repeat the bounded extraction behavior.
- Line 72: Replace the fixed livemaid-test session with an agent-specific tmux
session throughout the documentation: update AGENTS.md lines 72, 86-89, 103-107,
and 123-124; reference/standards/browser-mcp.md lines 13-18 and 24-25; and
reference/standards/testing.md line 28. Document using the agent-specific
session for startup, retry, cleanup, ownership, and server-table entries, while
preserving the concurrency claim only when session isolation is guaranteed.

In `@playwright.config.ts`:
- Around line 11-13: Ensure the URL used by the use.baseURL configuration is
assigned before Playwright resolves playwright.config.ts, rather than relying on
globalSetup to set it afterward. Update the global setup and configuration flow
so relative-URL tests use the randomly selected server port, while preserving
the PLAYWRIGHT_BASE_URL override when it is already provided.

In `@scripts/playwright-global-setup.mjs`:
- Around line 5-10: Replace the local random-port implementation in
scripts/playwright-global-setup.mjs#L5-L10 with an import of getRandomPort from
scripts/get-random-port.mjs and await it in globalSetup. Apply the same import
and awaited allocator call in scripts/start-test-server.mjs#L5-L10 so both
launchers use the checked port allocator.
- Around line 20-45: Supervise child-process startup and shutdown: in
scripts/playwright-global-setup.mjs lines 20-45, reject readiness on proc error
or early exit, clear the readiness timeout when settled, and await proc
termination during teardown; in scripts/start-test-server.mjs lines 17-28,
handle child error and exit events so launch failures reach main().catch, and
preserve a nonzero child exit status.

In `@src/components/Dashboard.tsx`:
- Around line 167-195: Update fetchData’s isFetchingRef guard to record a
pending refresh request instead of returning silently. After the active fetch
completes, clear the pending state and trigger exactly one silent fetchData
refresh, ensuring this runs after isFetchingRef is reset and does not interfere
with the existing loading behavior.

In `@src/components/editor/InlineTextEditor.tsx`:
- Around line 496-505: Update both the capture- and bubble-phase Ctrl/Cmd+A
handlers in LiveMaidEditor.tsx to bypass their textarea selection logic whenever
isInlineEditing is true, including the preventDefault and removeAllRanges calls.
Preserve the existing textarea behavior for non-inline editing so
InlineTextEditor’s content-editable onKeyDown handler can perform selection.
- Around line 19-75: Export the shared formatting helpers from
InlineTextEditor.tsx, including isFormatTag, isAllContentFormatted,
getTextNodesInRange, and coversAllContent, then remove their duplicate
implementations from handleFormatText in LiveMaidEditor.tsx and import the
shared symbols there. Ensure both toolbar state and formatting use the
collapsed-range behavior implemented by the shared getTextNodesInRange.
- Around line 310-318: Update the sizing logic in the useLayoutEffect for
InlineTextEditor so measuredHeight uses the element’s transform-independent
layout height via offsetHeight instead of getBoundingClientRect().height.
Preserve the existing height calculations and state update, then verify the
editor sizing across multiple zoom levels and iterate on any rendering issues
found.

In `@src/components/editor/LiveMaidEditor.tsx`:
- Around line 2992-3000: Update the auto-added closing-tag cleanup near the
latest editing text handling so alignment wrappers created by handleFormatText
remain balanced. Track or otherwise identify the <div style="text-align: …">
wrapper added during the current edit, and exclude its matching opening and
closing tags from removal; only strip browser-inserted tags not introduced by
handleFormatText, preserving normalizeHtmlForMermaid’s alignment wrapper
behavior.
- Around line 2915-2941: The active-format path in the editor’s format toggle
must stop calling global unwrapAll(fmt) for partial selections. Update the
bold/italic handling around coversAllContent, range, and unwrapAll so it
extracts the selected fragment, removes only matching format elements within
that fragment, reinserts it, and unwraps any ancestor format element fully
covered by the selection; retain whole-editor unwrapping for full selections and
preserve the existing wrapping behavior.

In `@src/components/editor/SequenceToolbar.tsx`:
- Around line 209-243: Update the participant picker flow around
showParticipantPicker and its trigger to close when clicking outside the picker
or pressing Escape, following the existing outside-click listener pattern used
by nearby popovers such as the shape picker and sequence plus menu. Add
aria-expanded and aria-haspopup to the trigger, preserving selection behavior,
then verify the dismissal interaction in the browser and fix any issues found
during testing.

In `@src/hooks/useCanvasInteraction.ts`:
- Around line 3517-3532: Escape Mermaid node IDs before interpolating them into
regular-expression sources in both sites: src/hooks/useCanvasInteraction.ts
lines 3517-3532 and src/components/editor/LiveMaidEditor.tsx lines 3299-3312. In
the extraction logic using quoteBracketRegex and nodeRegex, create one escaped
value from targetNodeId or selectedNodeId with the existing regex-escape pattern
and use it consistently in both regexes, matching the escapedNodeId approach in
handleChangeShape.
- Around line 2437-2442: Update the newTextBox geometry selection so sequence
messages use their label rect and sequence notes use the .noteText label rect,
while flowchart and cluster paths continue using the full element rect. Remove
the now-unused textRect assignments in the affected branches, then verify
placement for a flowchart node, sequence message, and sequence note through the
required browser screenshot/evaluation iteration.

In `@src/lib/telemetry/index.ts`:
- Around line 14-15: Use distinct storage keys for persisted telemetry
preferences and the transient dashboard refresh message: update the dashboard
transport in dashboardSync.ts to use its own key instead of STORAGE_KEY, while
keeping STORAGE_KEY dedicated to telemetry. If STORAGE_KEY itself must change,
add migration logic to preserve existing telemetry preferences before falling
back to defaults.

In `@src/lib/utils.ts`:
- Around line 45-74: Update normalizeHtmlForMermaid to sanitize pasted HTML
before it is stored or rendered: remove script, style, event-handler, and other
disallowed active content and strip unsafe attributes while preserving only the
markup required for Mermaid labels. Ensure the sanitized result is used by the
editingText innerHTML flow or sanitize the final SVG before DOM insertion,
covering non-block tags that the current replacements leave unchanged.

---

Outside diff comments:
In `@src/components/editor/LiveMaidEditor.tsx`:
- Around line 3216-3231: Use the existing normalizedText value in the flowchart
node, edge-label, and block-label save paths instead of raw latestEditingText or
newline-only replacement. Update the unchanged-label comparison near
currentLabel and the cluster rename value near editingTextForSave, plus the
block-label handling around the loop/alt path, so editor div markup becomes
Mermaid-compatible line breaks and formatting is not written into source.

---

Minor comments:
In `@AGENTS.md`:
- Around line 78-80: Label the Markdown code fence containing “Starting test dev
server on http://localhost:<port>” with an appropriate language such as text or
console to satisfy markdownlint MD040.

In `@reference/features/editor/overview.md`:
- Around line 27-29: Update the Flowchart inline editors bullet in the reference
to describe the implemented flex layout with centered alignment, matching
InlineTextEditor rather than documenting display: table. Retain the existing
font and whitespace behavior only if it remains accurate.

In `@src/components/editor/InlineTextEditor.tsx`:
- Line 487: Update the inline editor styling in InlineTextEditor to use the
active theme instead of hard-coded white background, dark text, and fixed
typography. Read the relevant background, text color, and font properties from
theme or the edited element’s computed style, and apply them to the editor so
dark and redux overlays match the node being replaced.

In `@src/components/editor/LiveMaidEditor.tsx`:
- Around line 2861-2886: Update the alignment wrapper lookups in the
format-handling logic and wrapEntireContent to only match a direct child of el,
not nested descendants. Replace the broad div[style*='text-align'] queries with
a scoped direct-child selector so alignment always targets the top-level
wrapper.
- Around line 3005-3014: Update the no-change early-return branch in the inline
editor flow around normalizeHtmlForMermaid to close editing without clearing the
current selection state. Remove the selectedNodeId, selectedSvgId, selectionBox,
and textBox resets, matching the existing unchanged-content handling near the
corresponding no-change path.
- Around line 3305-3322: Update the replacement calls in the quoteBracket,
nodeRegex, and standaloneRegex branches to use replacer functions instead of
replacement strings, ensuring editingTextForSave is inserted verbatim and
user-provided $ sequences are not interpreted as replacement tokens.

In `@src/components/editor/SequenceToolbar.tsx`:
- Around line 162-170: Update the identifier-generation logic near the newId
search so every fallback candidate is checked against usedIds before being
selected. Continue generating candidates until an unused identifier is found,
and preserve the existing A–Z preference while ensuring the returned identifier
cannot collide with an existing participant.

In `@src/hooks/useCanvasInteraction.ts`:
- Around line 3672-3678: Update the comment-mode shape-click branch in the
interaction handler to invoke commitEditRef.current?.() before
onShapeCommentPlace and its early return, ensuring inline edits are committed
and editing state is cleared before opening the comment composer. Apply the same
commit guard to the message-band comment-mode branch as well.

In `@src/lib/dashboardSync.ts`:
- Around line 19-35: Ensure the dashboard refresh notification is sent only once
per request: in the BroadcastChannel block, return immediately after a
successful postMessage and close operation. Keep the localStorage fallback in
the surrounding flow for cases where BroadcastChannel is unavailable or throws,
preserving onDashboardRefresh compatibility.

In `@src/lib/utils.ts`:
- Around line 16-27: In the HTML entity decoding chain, move the `&amp;`
replacement to after the `&lt;`, `&gt;`, `&quot;`, `&`#39`;`, and `&nbsp;`
replacements so encoded entities such as `&amp;lt;` remain literal `&lt;` text.
Leave the surrounding tag stripping and whitespace normalization unchanged.

In `@src/test/dashboard-cross-tab-sync.spec.ts`:
- Around line 139-152: In the dashboard cross-tab sync test, update the
initial-load assertions after navigating `otherPage` to wait for the `Alpha
Flow` diagram to be visible before assigning `updatedDiagrams`. Use a specific
diagram-card assertion rather than the heading visibility check, ensuring the
initial fetch completes before the mock response changes.

In `@src/test/inline-text-editor.test.tsx`:
- Around line 8-12: Update the test cleanup around the beforeEach setup for el
by adding an afterEach hook that removes the created editor element from
document.body and clears the test reference as appropriate. Ensure each test
leaves no editor or selection-related DOM state behind.

---

Nitpick comments:
In `@src/components/editor/EditorCanvas.tsx`:
- Around line 2465-2499: Replace the enumerated selectors in the isCommentMode
style block with one descendant rule targeting .mermaid-container and all of its
descendants, using the existing cursor: copy !important declaration. Keep the
rule within the comment-mode conditional while covering future Mermaid elements
without listing individual classes.
- Around line 808-815: Remove the redundant [data-class-text-editor] selector
from the generic floating-UI guard in the hitFloatingUi check within
EditorCanvas, leaving [data-inline-editor] to cover the shared wrapper. Retain
[data-class-text-editor] only in guards or logic that requires
class-editor-specific behavior.

In `@src/components/editor/InlineTextEditor.tsx`:
- Around line 119-172: Extract the repeated ancestor-walk logic from the
selection handling into a single format-detection helper, parameterized by the
format name such as “bold” or “italic.” Use that helper for both format checks
across the invalid-selection, collapsed/full-content, and partial-selection
branches while preserving the existing empty-selection fallback behavior.
- Around line 377-474: The five toolbar buttons in the editor repeat identical
event handling and lack accessibility state. Extract a local FormatButton
component accepting format, label, icon, and active; move the shared mouse and
click behavior into it, set aria-pressed from active, and provide aria-label
from label, then replace the bold, italic, and alignment button markup while
preserving their existing formats, icons, titles, and active styling.

In `@src/components/editor/LiveMaidEditor.tsx`:
- Line 3083: In the LiveMaidEditor branch logic, replace each repeated
normalizeHtmlForMermaid(latestEditingText) call with the existing normalizedText
value computed earlier, including all four referenced branches. Preserve the
current newText usage and avoid recomputing the pure normalization.
- Around line 2291-2297: In the sequence message handling around
sequenceMessageIndex, replace the combined finite-and-null checks with a single
Number.isFinite(sequenceMessageIndex) guard, since malformed IDs produce NaN and
null is not finite. Apply the same simplification to the conditional at the
later endpoint-processing branch, preserving the existing geometry lookup and
handling.

In `@src/components/editor/SequenceToolbar.tsx`:
- Around line 112-122: Update handleAddParticipant to derive displayName from
the existing PARTICIPANT_TYPES definitions instead of maintaining the duplicate
displayNames map. Reuse the resolved label when constructing the “New”
participant name, including the existing fallback behavior if needed, and remove
the redundant label list.

In `@src/hooks/useCanvasInteraction.ts`:
- Around line 1544-1554: Extract the repeated focus-and-select-all logic into a
module-scope helper named focusAndSelectAll near the other DOM helpers,
accepting an HTMLElement or null and safely returning when absent. Replace the
four duplicated blocks in the interaction flows with calls to this helper while
preserving their existing timeout behavior.
- Around line 3548-3595: Extract the duplicated edge-label resolution logic from
handleEditClick and the current fallback block into a shared helper such as
resolveEdgeLabelText(container, rawSvgId), then use it at both call sites while
preserving the existing text extraction behavior. Update the nearby comment to
remove the incorrect “path index / 2” statement and describe direct indexing of
the filtered edge paths.
- Line 9: Remove the unused containsHtml import from useCanvasInteraction.ts,
leaving the remaining imports and hook implementation unchanged.

In `@src/lib/telemetry/telemetryProvider.tsx`:
- Line 39: Move the localStorage write out of saveTelemetryPreferences calls
inside the functional setUsageAnalytics and setDebugReporting updaters. Add an
effect in the telemetry provider that persists the current usageAnalytics and
debugReporting values after state changes, while keeping the state updaters
pure.

In `@src/test/html-utils.test.ts`:
- Around line 38-45: Extend the tests for htmlToPlainText and
normalizeHtmlForMermaid to cover the missing edge cases: assert that “&amp;lt;”
remains “&lt;” after decoding, and assert that nested empty formatting tags such
as “<b><i></i></b>” normalize to the intended empty result without residual
outer tags.

In `@src/test/inline-text-editor.test.tsx`:
- Around line 31-123: Add a test covering the no-selection path in
getActiveFormats: set the editor content to fully bold text, clear all ranges
with window.getSelection()?.removeAllRanges(), then assert the returned formats
indicate bold true, italic false, and an empty alignment. Keep the existing
selection-based tests unchanged.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5eb6c27c-9f23-4669-94a3-7db0e6585f69

📥 Commits

Reviewing files that changed from the base of the PR and between 460baec and df229f7.

⛔ Files ignored due to path filters (1)
  • smoke-test-1-editor.png is excluded by !**/*.png
📒 Files selected for processing (28)
  • .gitignore
  • AGENTS.md
  • eslint.config.mjs
  • next.config.ts
  • package.json
  • playwright.config.ts
  • reference/features/editor/overview.md
  • reference/standards/browser-mcp.md
  • reference/standards/testing.md
  • scripts/get-random-port.mjs
  • scripts/playwright-global-setup.mjs
  • scripts/start-test-server.mjs
  • src/components/Dashboard.tsx
  • src/components/editor/ClassTextEditor.tsx
  • src/components/editor/EditorCanvas.tsx
  • src/components/editor/InlineTextEditor.tsx
  • src/components/editor/LiveMaidEditor.tsx
  • src/components/editor/SequenceToolbar.tsx
  • src/hooks/useCanvasInteraction.ts
  • src/lib/dashboardSync.ts
  • src/lib/diagrams/sequence.tsx
  • src/lib/telemetry/index.ts
  • src/lib/telemetry/telemetryProvider.tsx
  • src/lib/utils.ts
  • src/test/dashboard-cross-tab-sync.spec.ts
  • src/test/html-utils.test.ts
  • src/test/inline-text-editor.test.tsx
  • tsconfig.json

Comment thread AGENTS.md
Comment thread AGENTS.md Outdated
Comment thread playwright.config.ts
Comment thread scripts/playwright-global-setup.mjs Outdated
Comment thread scripts/playwright-global-setup.mjs
Comment thread src/components/editor/SequenceToolbar.tsx
Comment thread src/hooks/useCanvasInteraction.ts
Comment thread src/hooks/useCanvasInteraction.ts
Comment thread src/lib/telemetry/index.ts
Comment thread src/lib/utils.ts
Apply DOMPurify-based sanitization to all paths that write label HTML back
into the diagram code:
- sanitizeHtml() with strict allowlist (formatting-only tags, no event
  handlers, no javascript: URLs, style constrained to text-align only)
- Wired into InlineTextEditor innerHTML sink (CodeQL DOM text reinterpreted
  as HTML finding)
- Wired into LiveMaidEditor handleEditSubmit (flowchart-node and
  cluster-rename branches that previously wrote unsanitized innerHTML
  directly into the diagram source)
- Fixed htmlToPlainText double-unescaping with single-pass entity decode
- Added 56 unit tests (jsdom + Node fallback paths)

Addresses CodeQL findings: DOM text reinterpreted as HTML, Double escaping,
Incomplete multi-character sanitization (x4).
Comment thread src/lib/utils.ts Fixed
Comment thread src/lib/utils.ts Fixed
The regex for removing HTML comments now also strips unclosed comments
(<!-- without -->), so payloads like <!--<script>... are fully removed.
Found by CodeQL incomplete multi-character sanitization scan.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
scripts/playwright-global-setup.mjs (1)

14-16: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Set PLAYWRIGHT_BASE_URL before Playwright reads baseURL.

playwright.config.ts is evaluated when defineConfig is called and caches use.baseURL from process.env.PLAYWRIGHT_BASE_URL. The current globalSetup assigns that env var after the config is already loaded, so tests keep using http://localhost:3435 unless the config is rewritten to read process.env.PLAYWRIGHT_BASE_URL in a later Playwright hook.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/playwright-global-setup.mjs` around lines 14 - 16, Set
PLAYWRIGHT_BASE_URL before Playwright loads or evaluates playwright.config.ts,
rather than assigning it only in the global setup after defineConfig has cached
use.baseURL. Update the startup flow around the baseURL and NEXT_TEST_DIST_DIR
assignments so the configured port is available during config evaluation,
preserving the dynamically selected server URL.
🧹 Nitpick comments (2)
src/lib/utils.ts (1)

107-120: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

constrainStyleAttributes discards other attributes when it keeps a style value.

ALLOWED_ATTR permits class, but line 114 rebuilds the tag from the name and the style value only. A tag such as <div class="x" style="text-align:center"> loses class. The non-matching branch at line 117 keeps the other attributes, so the two branches are inconsistent.

Preserve the remaining attributes in both branches, or drop class from ALLOWED_ATTR to make the behavior explicit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/utils.ts` around lines 107 - 120, Update constrainStyleAttributes so
the allowed-style branch preserves all original non-style attributes, including
class, instead of rebuilding the tag from only the tag name and style value.
Keep the existing style validation and ensure both branches retain consistent
remaining attributes.
src/test/inline-editor-formatting.spec.ts (1)

26-31: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Replace the fixed 2000 ms wait with a state-based wait.

Line 28 adds a fixed delay to every test in this file. A fixed delay is slow when rendering finishes early and flaky when rendering is slow. Wait for the target node instead.

♻️ Proposed refactor
   await page.waitForSelector("svg[id^='mermaid-svg']", { timeout: 20000 });
-  await page.waitForTimeout(2000);
   const svg = page.locator("svg[id^='mermaid-svg']");
   const node = svg.locator("g.node").filter({ hasText: "Start" }).first();
+  await expect(node).toBeVisible({ timeout: 20000 });
   await node.dblclick({ force: true });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/test/inline-editor-formatting.spec.ts` around lines 26 - 31, Replace the
fixed waitForTimeout call in the inline-editor test before node.dblclick with a
state-based wait for the target node identified by the “Start” text. Ensure the
node is visible or otherwise ready before invoking dblclick, while preserving
the existing SVG and node locator flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/editor/SequenceToolbar.tsx`:
- Around line 121-122: Update PARTICIPANT_DECL_WITH_LABEL_RE to allow zero or
more whitespace characters between the participant name and the optional @{}
type block, so declarations generated by buildSequenceParticipantInsertLine are
matched and getSequenceParticipantLabels does not create duplicate labels.
- Around line 146-152: The fallback in generateSequenceParticipantId must avoid
collisions with existing and concurrently generated IDs. After exhausting
single-letter IDs, generate P-prefixed candidates and check each against usedIds
before returning; ensure repeated calls in the same millisecond or second
continue producing unused candidates.

In `@src/test/comment-mode.spec.ts`:
- Around line 27-37: In the comment-mode test, update the node interaction after
locating `node` to wait explicitly for the node to be visible, then click it
without `{ force: true }`. Preserve the existing locator and comment-composer
assertion flow while relying on Playwright’s normal actionability checks.

In `@src/test/flowchart-edge-label.spec.ts`:
- Around line 56-85: The cleanup in src/test/flowchart-edge-label.spec.ts lines
56-85 must run in a finally block and assert the DELETE response is OK; update
the test around its existing request and assertion flow without changing the
rendering checks. In src/test/sequence-multi-line.spec.ts lines 33-36, retain
the existing cleanup request and assert its DELETE response is OK, since
successful deletion returns 204.

In `@src/test/html-utils.test.ts`:
- Around line 216-226: Run npm run prepush and apply its Prettier formatting to
all three affected test files: collapse the wrapped normalizeHtmlForMermaid
arguments in src/test/html-utils.test.ts lines 216-226, the wrapped sanitizeHtml
arguments in src/test/html-sanitize-jsdom.test.ts lines 26-36, and the
destructured { page } test argument in src/test/inline-editor-formatting.spec.ts
lines 88-90; commit the formatted output without bypassing validation steps.

In `@src/test/inline-editor-formatting.spec.ts`:
- Around line 130-136: Update the test “empty save closes silently and keeps the
label” to clear the inline editor’s existing “Start” content after openEditor
and before pressing Control+Enter. Use the editor locator associated with
contenteditable to remove its text, then retain the existing close and label
assertions.

In `@tsconfig.json`:
- Around line 37-53: Remove all machine-specific absolute temporary type globs
from the tsconfig include configuration, retaining only stable project-relative
Next.js type patterns such as the existing "**/*.mts" entry. Ensure temporary
test-server output remains excluded via the existing "tmp" exclusion and does
not require paths outside the project directory.

---

Outside diff comments:
In `@scripts/playwright-global-setup.mjs`:
- Around line 14-16: Set PLAYWRIGHT_BASE_URL before Playwright loads or
evaluates playwright.config.ts, rather than assigning it only in the global
setup after defineConfig has cached use.baseURL. Update the startup flow around
the baseURL and NEXT_TEST_DIST_DIR assignments so the configured port is
available during config evaluation, preserving the dynamically selected server
URL.

---

Nitpick comments:
In `@src/lib/utils.ts`:
- Around line 107-120: Update constrainStyleAttributes so the allowed-style
branch preserves all original non-style attributes, including class, instead of
rebuilding the tag from only the tag name and style value. Keep the existing
style validation and ensure both branches retain consistent remaining
attributes.

In `@src/test/inline-editor-formatting.spec.ts`:
- Around line 26-31: Replace the fixed waitForTimeout call in the inline-editor
test before node.dblclick with a state-based wait for the target node identified
by the “Start” text. Ensure the node is visible or otherwise ready before
invoking dblclick, while preserving the existing SVG and node locator flow.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 6b43e604-89aa-4618-b586-1949876808cb

📥 Commits

Reviewing files that changed from the base of the PR and between df229f7 and eb4e42a.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (18)
  • package.json
  • playwright.config.ts
  • scripts/playwright-global-setup.mjs
  • src/components/editor/EditorCanvas.tsx
  • src/components/editor/InlineTextEditor.tsx
  • src/components/editor/LiveMaidEditor.tsx
  • src/components/editor/SequenceToolbar.tsx
  • src/lib/utils.ts
  • src/test/comment-mode.spec.ts
  • src/test/flowchart-edge-label.spec.ts
  • src/test/html-sanitize-jsdom.test.ts
  • src/test/html-utils.test.ts
  • src/test/inline-editor-formatting.spec.ts
  • src/test/sequence-multi-line.spec.ts
  • src/test/sequence-participant-logic.test.ts
  • src/test/sequence-toolbar.spec.ts
  • src/test/telemetry-persistence.test.ts
  • tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (4)
  • playwright.config.ts
  • src/components/editor/EditorCanvas.tsx
  • src/components/editor/LiveMaidEditor.tsx
  • src/components/editor/InlineTextEditor.tsx

Comment thread src/components/editor/SequenceToolbar.tsx
Comment thread src/components/editor/SequenceToolbar.tsx Outdated
Comment thread src/test/comment-mode.spec.ts Outdated
Comment thread src/test/flowchart-edge-label.spec.ts Outdated
Comment thread src/test/html-utils.test.ts
Comment thread src/test/inline-editor-formatting.spec.ts
Comment thread tsconfig.json Outdated
- Port allocator: extract shared getRandomPort from scripts (AGENTS.md, Playwright setup, start-test-server)
- Process cleanup: SIGTERM + SIGKILL fallback with timeout in Playwright teardown
- measuredHeight: divide getBoundingClientRect height by CSS scale factor
- Select-all: use Range API for contentEditable nodes instead of deprecated execCommand
- Closing-tag strip: detect alignment wrapper before stripping </div>
- RegExp escaping: add escapeRegExp util, apply to all user-derived nodeId patterns
- Formatting helpers: extract shared helpers to formatting-helpers.ts (InlineTextEditor + LiveMaidEditor)
- Storage keys: verified no collision between telemetry and dashboard-sync keys
- Unbolding partial selection: out of scope (tracked for future)
- textBox sequence-message: out of scope (tracked for future)

Closes #128

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/lib/formatting-helpers.ts`:
- Around line 6-10: Run the repository’s configured Prettier command on
formatting-helpers.ts and apply its generated formatting changes without
altering the behavior of isFormatTag.
- Around line 18-25: Update the HTMLElement branch in the range-to-text-node
logic around node === el to handle collapsed caret positions adjacent to element
children: when child is an element, descend to its first text node, or when
using the preceding sibling, descend to its last text node, before adding it to
textNodes. Preserve the existing direct text-node handling and ensure
getActiveFormats() receives the adjacent formatted text context.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 54f8e531-18b6-4653-b844-91709f8ce9df

📥 Commits

Reviewing files that changed from the base of the PR and between eb4e42a and 6fdc68e.

📒 Files selected for processing (9)
  • AGENTS.md
  • scripts/playwright-global-setup.mjs
  • scripts/start-test-server.mjs
  • src/components/editor/InlineTextEditor.tsx
  • src/components/editor/LiveMaidEditor.tsx
  • src/hooks/useCanvasInteraction.ts
  • src/lib/formatting-helpers.ts
  • src/lib/utils.ts
  • tsconfig.json
🚧 Files skipped from review as they are similar to previous changes (7)
  • tsconfig.json
  • scripts/start-test-server.mjs
  • scripts/playwright-global-setup.mjs
  • src/components/editor/InlineTextEditor.tsx
  • AGENTS.md
  • src/hooks/useCanvasInteraction.ts
  • src/components/editor/LiveMaidEditor.tsx

Comment thread src/lib/formatting-helpers.ts
Comment thread src/lib/formatting-helpers.ts
- SequenceToolbar: collision-free fallback ID generation (check usedIds)
- Comment-mode test: remove force:true to respect actionability checks
- Flowchart-edge-label test: assert delete response success
- Inline-editor test: clear editor content before testing empty save
- tsconfig.json: remove stale absolute paths to test server tmp dirs
- Prettier: reformat test files

Closes #128
…atting helpers

getTextNodesInRange now digs into element children when the caret is
positioned next to an element (e.g. <b>text</b>plain), finding text
nodes inside elements rather than returning empty. Fixes
getActiveFormats() returning incorrect results for collapsed selections
adjacent to formatted elements.
Comment thread src/lib/utils.ts
Comment thread src/components/editor/SequenceToolbar.tsx
Comment thread src/components/editor/SequenceToolbar.tsx
Comment thread src/test/comment-mode.spec.ts
Comment thread src/test/flowchart-edge-label.spec.ts
Comment thread src/test/html-utils.test.ts
Comment thread src/test/inline-editor-formatting.spec.ts
Comment thread src/lib/formatting-helpers.ts
Comment thread src/lib/formatting-helpers.ts
Comment thread tsconfig.json
@peter6055 peter6055 changed the title feat(editor): WYSIWYG inline editing with formatting toolbar, canvas UX fixes, and dashboard cross-tab sync feat(editor): inline editing with formatting toolbar, canvas UX fixes, and dashboard cross-tab sync Aug 7, 2026
@peter6055

peter6055 commented Aug 7, 2026 •

Copy link
Copy Markdown
Owner Author

@CodeRabbit full review

@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

@peter6055: I will perform a complete review of pull request #126.

⚠️ Action not completed

Review rate limited.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 56 minutes.

@coderabbitai

coderabbitai Bot commented Aug 7, 2026 •

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.


Your included review limit is currently reached under our Fair Usage Limits Policy. This review may still proceed through usage-based billing if eligible. Your next included review will be available in 56 minutes.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR upgrades LiveMaid’s inline diagram text editing from a plain textarea to a WYSIWYG contentEditable experience (toolbar formatting + HTML round-tripping), tightens canvas interaction/geometry so edits match Mermaid’s rendered layout, adds cross-tab Dashboard refresh syncing, persists telemetry preferences, and introduces a random-port Playwright test-server workflow with new unit/e2e coverage.

Changes:

  • Replace inline text editing with a contentEditable editor + floating formatting toolbar (bold/italic/alignment) and HTML sanitization/normalization for Mermaid labels.
  • Improve canvas UX (selection/interaction guards, comment-mode behavior, text box geometry) and extract the Sequence participant toolbar into a client-safe module.
  • Add Dashboard cross-tab sync plus new/updated Vitest + Playwright tests and Playwright global setup for random-port dev servers.

Reviewed changes

Copilot reviewed 35 out of 39 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tsconfig.json Adds tmp/ to TS exclude list (and formatting changes).
src/lib/utils.ts Introduces HTML sanitization + HTML↔text helpers used by inline editor save paths.
src/lib/formatting-helpers.ts Shared DOM selection/format detection helpers for editor formatting.
src/components/editor/InlineTextEditor.tsx Implements contentEditable inline editor + formatting toolbar + active format detection.
src/components/editor/LiveMaidEditor.tsx Reworks formatting + save flow to operate on DOM/HTML, adds comment-mode shape anchoring + dashboard refresh broadcast on create.
src/hooks/useCanvasInteraction.ts Updates geometry/selection logic for the new inline editor and comment placement on shapes/bands.
src/components/editor/EditorCanvas.tsx Adds additional floating-UI guards, comment-mode cursor behavior, and threads formatting handler through.
src/components/editor/ClassTextEditor.tsx Adds inline-editor guards and aligns typography to match updated inline editor.
src/lib/diagrams/sequence.tsx Makes the sequence plugin server-safe and re-exports toolbar helpers from client module.
src/components/editor/SequenceToolbar.tsx New extracted participant picker + participant insertion/auto-ID logic.
src/lib/dashboardSync.ts Adds BroadcastChannel/localStorage-based cross-tab dashboard refresh signaling.
src/components/Dashboard.tsx Adds visibility-based refresh + cross-tab refresh listener; broadcasts refresh after creation.
src/lib/telemetry/index.ts Persists telemetry preferences via a stable storage key and restores them on init.
src/lib/telemetry/telemetryProvider.tsx Uses the unified telemetry storage key and bootstraps provider state from persisted prefs.
package.json Adds dompurify dependency and updates test:dev to random-port server starter.
package-lock.json Locks dependency updates (notably dompurify and js-yaml).
next.config.ts Supports per-test-server distDir and tightens default allowedDevOrigins behavior.
scripts/get-random-port.mjs Adds random available port selection helper for test servers.
scripts/start-test-server.mjs Starts a random-port Next dev server with unique distDir for agent testing.
scripts/playwright-global-setup.mjs Playwright global setup to start/warm a random-port test server and expose PLAYWRIGHT_BASE_URL.
playwright.config.ts Switches to globalSetup + env-driven baseURL; increases timeout.
eslint.config.mjs Ignores tmp/** for linting.
.gitignore Ignores tmp/ test-server artifacts.
AGENTS.md Documents random-port test server and Playwright-first guidance.
reference/standards/testing.md Updates dev-server/testing guidance to match random-port workflow.
reference/standards/browser-mcp.md Adds guidance on when to prefer Playwright vs Browser MCP and recovery steps.
reference/features/editor/overview.md Documents inline editor layout requirements relative to Mermaid foreignObject rendering.
src/test/html-utils.test.ts Adds unit tests for HTML helpers/sanitization/normalization.
src/test/html-sanitize-jsdom.test.ts Validates DOMPurify/jsdom sanitization path behavior.
src/test/inline-text-editor.test.tsx Unit tests for getActiveFormats() selection/caret behavior.
src/test/inline-editor-formatting.spec.ts E2E tests for inline toolbar formatting and save/cancel behaviors.
src/test/telemetry-persistence.test.ts Unit tests for telemetry preference persistence + gating behavior.
src/test/dashboard-cross-tab-sync.spec.ts E2E tests for dashboard refresh on visibility change and BroadcastChannel message.
src/test/comment-mode.spec.ts E2E test verifying comment-mode shape anchoring/composer flow.
src/test/sequence-toolbar.spec.ts E2E tests for extracted sequence participant picker behavior.
src/test/sequence-participant-logic.test.ts Unit tests for sequence participant auto-ID/label/insertion logic.
src/test/sequence-multi-line.spec.ts Makes the sequence multi-line suite self-seeding and stabilizes dblclick targeting.
src/test/flowchart-edge-label.spec.ts Improves reliability by self-seeding flowchart edge-label test and reduces flakiness.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/lib/dashboardSync.ts
Comment thread src/components/editor/LiveMaidEditor.tsx
Comment thread src/lib/utils.ts Outdated
Comment thread src/lib/utils.ts Fixed
Use DOM textContent / repeated stripping for htmlToPlainText, loop empty-tag cleanup in normalizeHtmlForMermaid, skip localStorage when BroadcastChannel succeeds, escape selectedNodeId in save/delete regexes, and remove unused DANGEROUS_* constants.

Co-authored-by: Cursor <cursoragent@cursor.com>
@railway-app

railway-app Bot commented Aug 7, 2026 •

Copy link
Copy Markdown

🚅 Deployed to the livemaid-pr-126 environment in livemaid

Service Status Web Updated (UTC)
livemaid ✅ Success (View Logs) Web Aug 7, 2026 at 1:43 pm

@railway-app
railway-app Bot temporarily deployed to livemaid / livemaid-pr-126 August 7, 2026 12:49 Destroyed
@peter6055
peter6055 merged commit 23b9b20 into main Aug 7, 2026
7 checks passed
@peter6055
peter6055 deleted the feat/resolve-inline-editing-2-feature-improvement branch August 7, 2026 13:46

This branch was successfully deployed

No deployments
livemaid / livemaid-pr-126 — 176a9798 Deployed Aug 7, 2026 by railway-app[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants