Repository navigation
feat(editor): inline editing with formatting toolbar, canvas UX fixes, and dashboard cross-tab sync - #126
Conversation
- 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.
|
Warning Review limit reached
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 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 configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughThe 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. ChangesTest infrastructure isolation
Mermaid editor workflows
Dashboard refresh synchronization
Telemetry preference initialization
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
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 2📝 Generate docstrings 💡
🛠️ Fix failing CI checks 💡
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 liftThe 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\nexists in the input. A two-line flowchart node label is written to the Mermaid source asline1<div>line2</div>instead ofline1<br/>line2.Line 3216:
currentLabel !== latestEditingTextcompares 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
\ncollapsing, so<b>and<div>markup from the editor is written into theloop/altkeyword 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 winSend one refresh notification per request.
Lines 20-35 send a
BroadcastChannelmessage and astorageevent when both APIs work.onDashboardRefreshsubscribes to both, so each Dashboard tab can run its callback twice.Return after a successful
BroadcastChannelsend. UselocalStorageonly 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 winWait for the initial dashboard data before changing the mock response.
The heading is visible before
fetchDatacompletes. The initial request can readupdatedDiagramsafter Line 152, so this test can pass without a BroadcastChannel refresh.Wait for
Alpha Flowto be visible before assigningupdatedDiagrams.🤖 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 winLabel the Markdown code fence.
The fence at Line 78 triggers markdownlint MD040. Use
textorconsoleas 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 winThe editor colors are hard-coded and ignore the
themeprop.
bg-white,color: "#1c1c21", and the fixed font stack render a light editor over every Mermaid theme. On thedarkandreduxthemes 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
themebut does not read it. Derive the background and text color fromtheme, 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.
querySelectorreturns the first matching descendant in document order at any depth. Content pasted into the editor, or content produced bywrapEntireContentat lines 2838-2843, can contain an inner div carryingtext-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 winDecode
&last to avoid double-unescaping.The current order decodes
&first. An input such as&lt;becomes<and then<, which changes the user text. Move the&replacement after the other entity replacements.🐛 Proposed fix for entity decode order
.replace(/<[^>]+>/g, "") // Strip all other tags - .replace(/&/g, "&") // Decode entities - .replace(/</g, "<") + .replace(/</g, "<") // Decode entities .replace(/>/g, ">") .replace(/"/g, '"') .replace(/&`#39`;/g, "'") .replace(/ /g, " ") + .replace(/&/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 `&` replacement to after the `<`, `>`, `"`, `&`#39`;`, and ` ` replacements so encoded entities such as `&lt;` remain literal `<` 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 winCommit an in-progress inline edit before routing to the comment composer.
This early return runs before the
isInlineEditinghandling at lines 3714-3739. The comment-mode toolbar button insrc/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, andisInlineEditingstays 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 winThis 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, andtextBox. 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 winThe fallback identifier can collide with an existing participant.
P${Date.now().toString().slice(-3)}produces one of 1000 values and is never checked againstusedIds. 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.replaceinterprets$&,$1,$`, and$'inside the replacement string.editingTextForSavecomes from user text, so a label such asCost $1 per unitinserts 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 winRemove the editor element after each test.
beforeEachappends a newdivtodocument.bodyand nothing removes it. Every test leaves one behind. Assertions still pass because each test holds its ownel, but any later test that queriesdocumentsees 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 winThe documented
display: tabledoes not match the implementation.
src/components/editor/InlineTextEditor.tsxsetsdisplay: "flex"withalignItems: "center"andjustifyContent: "center"(lines 549-551). The PR description also states that the editor now uses a flex layout with centered alignment. Nodisplay: tableappears 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 valueCollapse 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 winExtract one toolbar button component and expose the toggle state.
The five buttons repeat the same
onMouseDown/onClickbody. 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
FormatButtonthat takesformat,label,icon, andactive, and addaria-pressed={active}andaria-labelto 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 winAdd a case for the no-selection branch.
getActiveFormatshas a distinct path when no range exists inside the editor: it falls back toisAllContentFormattedover the whole content (lines 122-125 ofInlineTextEditor.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 assertsgetActiveFormats(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 winExtract 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 winThis edge-label lookup duplicates the block at lines 3436-3479.
The same sequence runs earlier in
handleEditClick: resolveg.edgePaths, index the clicked path, index intog.edgeLabels, and readforeignObject div, foreignObject span. Extract one helper, for exampleresolveEdgeLabelText(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
allPathsalready excludes.flowchart-link-hit-target, so the code usesindexOfdirectly 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 valueRemove the unused
containsHtmlimport.
containsHtmlis imported insrc/hooks/useCanvasInteraction.tsbut 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 valueReplace 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-containeron 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].
ClassTextEditorsets 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 documentmousedown.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 winDerive the display name from
PARTICIPANT_TYPES.
displayNamesrepeats every label already declared inPARTICIPANT_TYPESat 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 valueReuse the
normalizedTextcomputed at line 3003.
normalizeHtmlForMermaid(latestEditingText)is a pure call with the same argument in all four branches, and the result is already stored innormalizedTextat line 3003. Replace eachconst newText = normalizeHtmlForMermaid(latestEditingText);withconst newText = normalizedText;, or usenormalizedTextdirectly.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 valueSimplify the repeated
sequenceMessageIndexvalidity 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.parseIntreturnsNaNfor a malformed id, so oneNumber.isFiniteguard 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 winAdd cases for double-unescaping and nested empty tags.
Two behaviours of the implementation are untested and both are currently wrong.
htmlToPlainTextdecodes&first, so&lt;becomes<instead of<. Addexpect(htmlToPlainText("&lt;")).toBe("<").
normalizeHtmlForMermaidremoves 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 “&lt;” remains “<” 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 winKeep the state updaters pure.
saveTelemetryPreferenceswrites tolocalStorage, butsetUsageAnalyticsandsetDebugReportingcall 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
usageAnalyticsanddebugReporting.🤖 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 `&`
replacement to after the `<`, `>`, `"`, `&`#39`;`, and ` `
replacements so encoded entities such as `&lt;` remain literal `<` 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 “&lt;”
remains “<” 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
⛔ Files ignored due to path filters (1)
smoke-test-1-editor.pngis excluded by!**/*.png
📒 Files selected for processing (28)
.gitignoreAGENTS.mdeslint.config.mjsnext.config.tspackage.jsonplaywright.config.tsreference/features/editor/overview.mdreference/standards/browser-mcp.mdreference/standards/testing.mdscripts/get-random-port.mjsscripts/playwright-global-setup.mjsscripts/start-test-server.mjssrc/components/Dashboard.tsxsrc/components/editor/ClassTextEditor.tsxsrc/components/editor/EditorCanvas.tsxsrc/components/editor/InlineTextEditor.tsxsrc/components/editor/LiveMaidEditor.tsxsrc/components/editor/SequenceToolbar.tsxsrc/hooks/useCanvasInteraction.tssrc/lib/dashboardSync.tssrc/lib/diagrams/sequence.tsxsrc/lib/telemetry/index.tssrc/lib/telemetry/telemetryProvider.tsxsrc/lib/utils.tssrc/test/dashboard-cross-tab-sync.spec.tssrc/test/html-utils.test.tssrc/test/inline-text-editor.test.tsxtsconfig.json
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).
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.
There was a problem hiding this comment.
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 winSet
PLAYWRIGHT_BASE_URLbefore Playwright readsbaseURL.
playwright.config.tsis evaluated whendefineConfigis called and cachesuse.baseURLfromprocess.env.PLAYWRIGHT_BASE_URL. The currentglobalSetupassigns that env var after the config is already loaded, so tests keep usinghttp://localhost:3435unless the config is rewritten to readprocess.env.PLAYWRIGHT_BASE_URLin 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
constrainStyleAttributesdiscards other attributes when it keeps astylevalue.
ALLOWED_ATTRpermitsclass, but line 114 rebuilds the tag from the name and thestylevalue only. A tag such as<div class="x" style="text-align:center">losesclass. 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
classfromALLOWED_ATTRto 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 winReplace 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
⛔ Files ignored due to path filters (1)
package-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (18)
package.jsonplaywright.config.tsscripts/playwright-global-setup.mjssrc/components/editor/EditorCanvas.tsxsrc/components/editor/InlineTextEditor.tsxsrc/components/editor/LiveMaidEditor.tsxsrc/components/editor/SequenceToolbar.tsxsrc/lib/utils.tssrc/test/comment-mode.spec.tssrc/test/flowchart-edge-label.spec.tssrc/test/html-sanitize-jsdom.test.tssrc/test/html-utils.test.tssrc/test/inline-editor-formatting.spec.tssrc/test/sequence-multi-line.spec.tssrc/test/sequence-participant-logic.test.tssrc/test/sequence-toolbar.spec.tssrc/test/telemetry-persistence.test.tstsconfig.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
- 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
There was a problem hiding this comment.
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
📒 Files selected for processing (9)
AGENTS.mdscripts/playwright-global-setup.mjsscripts/start-test-server.mjssrc/components/editor/InlineTextEditor.tsxsrc/components/editor/LiveMaidEditor.tsxsrc/hooks/useCanvasInteraction.tssrc/lib/formatting-helpers.tssrc/lib/utils.tstsconfig.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
- 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.
|
@CodeRabbit full review |
|
|
|
There was a problem hiding this comment.
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
contentEditableeditor + 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.
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>
|
🚅 Deployed to the livemaid-pr-126 environment in livemaid
|
Summary
Transforms the inline text editor from a plain
<textarea>into a WYSIWYGcontentEditableeditor 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 (MermaidforeignObjecttext 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 viainnerHTML; typography/layout tuned to match Mermaid's flowchart output (fontSize: 16,lineHeight: 1.5, Mermaid font stack,whiteSpace: break-spaces, flex + vertical centering). Added exportedgetActiveFormats()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—handleFormatTextrewritten 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 vianormalizeHtmlForMermaid, and treats no-op edits as silent closes. Flowchart node-label save now prefers a dedicatedNODE["..."]quoted-bracket regex (fixes labels containing parens).EditorCanvas.tsx— ThreadedhandleFormatTextthrough;[data-inline-editor]/[data-class-text-editor]added to pointer-down and double-click floating-UI guards.2. Inline editor formatting toolbar
scale(var(--zoom-inverse-scale, ...))).3. Inline editor UX fixes
useCanvasInteraction.ts—textBoxnow derives from the full node rect so the editor covers the rendered node and wraps lines like Mermaid;ref.select()replaced withRange.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 newonShapeCommentPlacecallback (refactor of the composer intoopenShapeCommentComposerRef).EditorCanvas.tsx— Comment mode forcescopycursor over diagram SVG elements and suppresses selection/hover/resize/note overlays.ClassTextEditor.tsx— Addeddata-inline-editor+ double-click guard; font bumped to 16px to match.4. Sequence diagram toolbar extraction
sequence.tsxslimmed to just the plugin definition (server-safe, no"use client"); newSequenceToolbar.tsxhosts the participant type picker (8 archetypes, auto-ID, right-side insertion).ParticipantIcon/PARTICIPANT_TYPESre-exported for compatibility.5. Dashboard cross-tab sync
dashboardSync.ts—BroadcastChannel("livemaid-dashboard-sync")with alocalStoragefallback; 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 tempdistDir, no project-lock conflicts for concurrent agents.playwright.config.ts— fixed-port webServer replaced withglobalSetup;baseURLfromPLAYWRIGHT_BASE_URL.next.config.ts—distDirreadsNEXT_TEST_DIST_DIR;allowedDevOriginsdefaults to[]unlessALLOWED_DEV_ORIGINSset.AGENTS.mdrandom-port workflow, newreference/standards/browser-mcp.md, updatedreference/standards/testing.md.7. New tests
html-utils.test.ts—htmlToPlainText,containsHtml,normalizeHtmlForMermaid.inline-text-editor.test.tsx—getActiveFormatsbold/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— newSTORAGE_KEY;initTelemetryrestores persisted usage/debug toggles fromlocalStorage(was hardcoded OFF).telemetryProvider.tsx— unified storage key (livemaid:telemetry), bootstraps instance from saved prefs in one pass.9. Utilities & misc
utils.ts— newhtmlToPlainText,containsHtml,normalizeHtmlForMermaidused by all inline-edit save paths.smoke-test-1-editor.png;tmp/ignored/excluded from lint & TS.Behavior / risk notes
inlineInputReftype changedHTMLTextAreaElement→HTMLDivElement(all in-repo call sites migrated).livemaid:telemetry-preferences→livemaid:telemetry) — previously saved prefs are orphaned once.next.config.ts); environments withoutALLOWED_DEV_ORIGINSlose implicit localhost/tailnet allowances (mitigated here by.env).Verification
npm run prepushpasses (typecheck, prettier, Vitest: 158 tests across 10 files, production build).Summary by CodeRabbit