Skip to content

feat(canvas): render real mermaid/flowchart diagrams (slice 4)#1411

Merged
jaylfc merged 3 commits into
devfrom
feat/canvas-mermaid-render
Jun 23, 2026
Merged

feat(canvas): render real mermaid/flowchart diagrams (slice 4)#1411
jaylfc merged 3 commits into
devfrom
feat/canvas-mermaid-render

Conversation

@jaylfc

@jaylfc jaylfc commented Jun 23, 2026

Copy link
Copy Markdown
Owner

Slice 4 of the tldraw to Excalidraw migration: mermaid/flowchart CanvasElements now render as real diagrams instead of placeholder rectangles.

What

  • mermaid-to-elements.ts: mermaidToExcalidraw(source, x, y) converts a mermaid source via @excalidraw/mermaid-to-excalidraw then convertToExcalidrawElements, translating the diagram to the element's position. Returns [] on blank/invalid source.
  • ExcalidrawBoard: an effect converts each mermaid/flowchart element asynchronously and pushes the scene through the imperative API; the placeholder rectangle shows while converting or on failure.

Verified

Screenshotted locally: a graph TD source renders as a flowchart (Start -> Decide diamond -> yes/Ship, no/Iterate) next to a sticky note, in Excalidraw's hand-drawn style. No console errors.

Tests

Converter unit tests (translate offset, empty/invalid -> []); board smoke tests; tsc clean; 64 ProjectsApp tests pass.

Remaining canvas slices

3 write-back interactions, 5 swap behind a flag (live verify), 6 remove tldraw.

Summary by CodeRabbit

  • New Features
    • Added support for rendering Mermaid and flowchart diagrams within the Excalidraw canvas.
    • Diagrams display as placeholder elements while converting in the background, with automatic updates upon completion.
    • Improved canvas rendering with dynamic scene updates and live element filtering.

…w board (slice 4)

Adds @excalidraw/mermaid-to-excalidraw and mermaid-to-elements.ts: convert a
mermaid/flowchart CanvasElement's source to positioned Excalidraw elements
(parseMermaidToExcalidraw -> convertToExcalidrawElements -> translate to the
element's x/y). ExcalidrawBoard runs the conversion in an effect and pushes the
result through the imperative API, falling back to the placeholder rectangle
while the async conversion runs or if the source is invalid.

Verified visually: a 'graph TD' source renders as a real flowchart (Start ->
Decide diamond -> yes/Ship, no/Iterate) beside a sticky note. Unit tests cover
the converter (translate + empty/invalid -> []); board smoke tests still pass;
tsc clean; 64 ProjectsApp tests green.
@jaylfc jaylfc enabled auto-merge (squash) June 23, 2026 15:26
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@jaylfc, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 25 minutes and 37 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

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 credits.

🚦 How do rate limits work?

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

For paid Pro and Pro+ PR reviews, CodeRabbit uses rolling per-developer review limits. Reviews become available again as older review attempts age out of the rolling limit window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: e8f10084-56d5-4c3d-852c-a9942ef9fe7e

📥 Commits

Reviewing files that changed from the base of the PR and between 5af005a and 56b0fa6.

📒 Files selected for processing (2)
  • desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx
  • desktop/src/apps/ProjectsApp/canvas/mermaid-to-elements.ts
📝 Walkthrough

Walkthrough

Adds @excalidraw/mermaid-to-excalidraw as a dependency and creates a new mermaid-to-elements.ts utility that parses Mermaid/flowchart source strings into offset-positioned Excalidraw elements. ExcalidrawBoard is updated to asynchronously convert diagram-kind canvas elements, store results in component state, compose the scene with placeholder fallbacks for pending conversions, and imperatively update the Excalidraw scene on each change.

Changes

Mermaid diagram rendering in Excalidraw canvas

Layer / File(s) Summary
mermaidToExcalidraw utility and package dependency
desktop/package.json, desktop/src/apps/ProjectsApp/canvas/mermaid-to-elements.ts, desktop/src/apps/ProjectsApp/__tests__/mermaid-to-elements.test.ts
Adds @excalidraw/mermaid-to-excalidraw to dependencies. Creates mermaid-to-elements.ts exporting the ExcalidrawElements type and the async mermaidToExcalidraw(source, offsetX, offsetY) function: trims input, returns [] for empty/invalid sources, parses via parseMermaidToExcalidraw, converts skeletons, and translates all element coordinates by the given offsets. Tests verify coordinate translation and [] return for blank or parse-error inputs.
ExcalidrawBoard async diagram state and scene update
desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx, desktop/src/apps/ProjectsApp/__tests__/ExcalidrawBoard.test.tsx
Adds DIAGRAM_KINDS, a live() helper filtering deleted elements and sorting by z_index, a diagrams state keyed by element id, and a diagramKey memo. A cancellable effect runs mermaidToExcalidraw conversions when diagramKey changes. Scene builder combines synchronous non-diagram elements with async results or placeholder rectangles. Imperative effect calls api.updateScene({ elements: sceneElements }) and conditionally fits the viewport. Existing test gets a parseMermaidToExcalidraw stub to avoid loading the real parser in jsdom.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 Hoppity-hop through the canvas wide,
Mermaid strings transformed inside,
Async conversion, offsets applied,
Placeholder rects while we bide,
The scene updates with elements aligned —
A diagram rabbit, beautifully timed! 🎨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 accurately and specifically describes the primary change: implementing real mermaid/flowchart diagram rendering as part of slice 4 of the Excalidraw migration.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/canvas-mermaid-render

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.

// Fallback: the placeholder rectangle from the base mapping.
return convertToExcalidrawElements([elementToSkeleton(el)] as unknown as SkeletonInput);
});
return [...regularScene, ...diagramScene];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: Z-ordering bug — regular elements always render below all diagram elements.

els is sorted by z_index ascending, but then you split it into regular (preserves relative z-order among regulars) and diagramScene (preserves relative z-order among diagrams), then concatenate [...regularScene, ...diagramScene]. Excalidraw draws in array order, so every diagram paints on top of every regular element regardless of z_index. A diagram with z_index: 1 will render over a note with z_index: 1000.

Merge the two arrays and re-sort by z_index before passing to convertToExcalidrawElements, or build a single skeleton list interleaved by z_index and pass the whole thing through one convertToExcalidrawElements call.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.


const sceneElements = useMemo(
// Re-run conversion only when a diagram element's id/source/position changes.
const diagramKey = useMemo(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: diagramKey invalidates on every unrelated element change, re-converting all diagrams.

The useMemo for diagramKey lists elements as its only dependency. elements is a fresh array reference whenever the scene changes (e.g., editing a note's text payload, dragging a sticky), so the memo recomputes JSON.stringify(...) and returns a new string even when the diagram-relevant inputs (id, x, y, source) are unchanged. A new diagramKey triggers the conversion effect, so adding/removing/editing any non-diagram element re-parses every mermaid source on the board.

Either drop the memoization and depend on a derived signature (e.g., a useMemo that returns the diagram list itself with elements as the dep), or hash only the relevant fields with a stable key.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

if (!api) return;
api.updateScene({ elements: sceneElements });
if (sceneElements.length > 0) {
api.scrollToContent(sceneElements, { fitToContent: true, animate: false });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: scrollToContent fires on every scene update, including async diagram completions.

The effect re-runs whenever sceneElements changes (initial mount, regular edits, and after each batch of async diagram conversions finishes). Calling scrollToContent(... { animate: false }) every time will jump the viewport to fit content each time a diagram finishes parsing, which is jarring when the user is panning/zoomed in. Consider only fitting on initial mount (when the API first becomes available) or when the content bounding box grows/shrinks meaningfully, not on every updateScene.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

}
(async () => {
const next: Record<string, ExcalidrawElements> = {};
for (const el of diagramEls) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Diagram conversions run serially with for...of await.

When N mermaid elements exist on the board, conversion takes ~Nx the per-diagram parse latency. parseMermaidToExcalidraw is independent per source, so await Promise.all(diagramEls.map(...)) (or batches) would let diagrams render in roughly the slowest single parse time instead of the sum.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

const converted = convertToExcalidrawElements(elements);
// Translate the diagram as a whole; bindings between nodes are relative, so
// shifting every element by the same offset preserves them.
return converted.map((el) => ({ ...el, x: el.x + offsetX, y: el.y + offsetY }));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

WARNING: Translating only x/y may leave arrow geometry misaligned.

The comment claims "shifting every element by the same offset preserves them" because bindings are relative. That is true for startBinding/endBinding (which Excalidraw resolves against referenced elements' current positions), but convertToExcalidrawElements may also produce elements with points arrays that are absolute coordinates relative to the canvas origin for some shape types. Shifting only x/y while leaving points unchanged will produce visual drift on those elements. Verify against the real @excalidraw/mermaid-to-excalidraw output (e.g., arrows and lines) and translate any positional arrays the library emits.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Jun 23, 2026

Copy link
Copy Markdown

Code Review Summary

Status: No New Issues Found | Recommendation: Approve (incremental)

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 0

The incremental diff (f7727ef6..56b0fa64) only changes desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx lines 6 and 78-91. It folds the cross-kind z-ordering fix.

Verification of changed lines

Lines 78-91 (sceneElements rewrite) — correctly addresses the previously outstanding WARNING about z-ordering. Walking live(elements) (already sorted by z_index) with flatMap interleaves regular and diagram elements by their relative z_index, so a regular element with a higher z_index is now rendered on top of a lower-z diagram. Per-element convertToExcalidrawElements calls match the prior per-element diagram-fallback pattern; elementToSkeleton returns a single skeleton per call so no grouping/binding semantics are lost.

Line 6 — drops the now-unused elementsToSkeletons import. Correct cleanup.

No new issues introduced on the changed lines.

Resolved since previous review
  • ExcalidrawBoard.tsx line ~92 — WARNING: Z-ordering bug, regular elements always below all diagrams ✅ FIXED in this incremental diff (lines 78-91)
Outstanding from previous review (on unchanged lines — out of scope for incremental review)
  • ExcalidrawBoard.tsx line 43 — WARNING: diagramKey invalidates on every unrelated element change, re-converting all mermaid diagrams (line 43 not changed by this diff)
  • mermaid-to-elements.ts line 25 — WARNING: Translating only x/y may leave arrow/line points misaligned (file not changed by this diff)
Files Reviewed (1 file)
  • desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx - 0 issues
Previous Review Summaries (2 snapshots, latest commit f7727ef)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit f7727ef)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1
Issue Details (click to expand)

SUGGESTION

File Line Issue
desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx 103 Fit-once can lock viewport to placeholder extent before async diagrams resolve
Resolved since previous review
  • ExcalidrawBoard.tsx line ~62 — serial for...of awaitPromise.all
  • ExcalidrawBoard.tsx line ~98 — scrollToContent on every update → fit-once via useRef
  • mermaid-to-elements.ts line ~26 — swallowed mermaid error → console.warn
Outstanding from previous review (still on changed lines)
  • ExcalidrawBoard.tsx line 92 — WARNING: Z-ordering bug, regular elements always below all diagrams regardless of z_index (unchanged)
  • ExcalidrawBoard.tsx line 43 — WARNING: diagramKey invalidates on every unrelated element change, re-converting all mermaid diagrams (unchanged)
  • mermaid-to-elements.ts line 25 — WARNING: Translating only x/y may leave arrow/line points misaligned (unchanged)
Files Reviewed (2 files)
  • desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx - 1 issue
  • desktop/src/apps/ProjectsApp/canvas/mermaid-to-elements.ts - 0 issues

Fix these issues in Kilo Cloud

Previous review (commit 5af005a)

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 3
SUGGESTION 2
Issue Details (click to expand)

WARNING

File Line Issue
desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx 89 Z-ordering bug: regular elements always render below all diagram elements regardless of z_index
desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx 43 diagramKey invalidates on every unrelated element change, re-converting all mermaid diagrams
desktop/src/apps/ProjectsApp/canvas/mermaid-to-elements.ts 25 Translating only x/y may leave arrow/line points misaligned

SUGGESTION

File Line Issue
desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx 98 scrollToContent fires on every scene update, including async diagram completions
desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx 62 Diagram conversions run serially with for...of await instead of Promise.all
Files Reviewed (5 files)
  • desktop/package.json - 0 issues
  • desktop/package-lock.json - 0 issues (generated)
  • desktop/src/apps/ProjectsApp/__tests__/ExcalidrawBoard.test.tsx - 0 issues
  • desktop/src/apps/ProjectsApp/__tests__/mermaid-to-elements.test.ts - 0 issues
  • desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx - 4 issues
  • desktop/src/apps/ProjectsApp/canvas/mermaid-to-elements.ts - 1 issue

Fix these issues in Kilo Cloud


Reviewed by minimax-m3 · Input: 43.3K · Output: 3.4K · Cached: 107.7K

Comment on lines +94 to 100
useEffect(() => {
if (!api) return;
api.updateScene({ elements: sceneElements });
if (sceneElements.length > 0) {
api.scrollToContent(sceneElements, { fitToContent: true, animate: false });
}
}, [api, sceneElements]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Performance: Viewport refits on every scene update, resetting pan/zoom

The effect calls api.updateScene(...) and then api.scrollToContent(..., { fitToContent: true }) every time sceneElements changes. Because diagrams resolve asynchronously, each diagram conversion (and any change to the elements prop) re-fits the viewport, snapping the view back to fit-content even if the user has panned/zoomed. With multiple diagrams resolving one-by-one (see sequential loop), the board will visibly jump several times on load.

Consider running scrollToContent only once (e.g. the first time api becomes available and the scene is non-empty), and using a ref/flag to avoid re-fitting on subsequent async scene pushes. While the board is read-only today, the refit-on-every-update behavior will be disruptive once interactions land in later slices.

Was this helpful? React with 👍 / 👎

Comment on lines +60 to +67
(async () => {
const next: Record<string, ExcalidrawElements> = {};
for (const el of diagramEls) {
const src = String((el.payload || {}).source ?? "");
next[el.id] = await mermaidToExcalidraw(src, el.x, el.y);
}
if (!cancelled) setDiagrams(next);
})();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Quality: Sequential awaits make multi-diagram conversion slow

The conversion loop awaits mermaidToExcalidraw for each diagram element serially inside a for loop, so total latency is the sum of all conversions. mermaid parsing is comparatively heavy; with several diagrams on a board the placeholder rectangles will persist noticeably longer than necessary. Each conversion is independent, so Promise.all(diagramEls.map(...)) would parallelize them and shorten time-to-render. If you prefer incremental display, set state per-element as each resolves instead of building one next map at the end.

Was this helpful? React with 👍 / 👎

Comment on lines +26 to +28
} catch {
return [];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Quality: Invalid mermaid is swallowed silently with no diagnostics

catch { return []; } discards the parse error entirely. Degrading to the placeholder rectangle is the intended UX, but with no console.warn/debug logging a malformed mermaid source becomes invisible to developers and users alike — the diagram just silently stays a placeholder with no indication why. Consider logging the caught error (at least in dev) so authoring mistakes in the source can be diagnosed.

Was this helpful? React with 👍 / 👎

@gitar-bot

gitar-bot Bot commented Jun 23, 2026

Copy link
Copy Markdown

Note

Your trial team has used its Gitar budget, so automatic reviews are paused. Upgrade now to unlock full capacity. Comment "Gitar review" to trigger a review manually.
Learn more about usage limits

Code Review 👍 Approved with suggestions 0 resolved / 3 findings

Integrates real mermaid diagram rendering by converting sources into Excalidraw elements. Address the viewport reset bug on scene updates, optimize conversion latency by parallelizing awaits, and improve error visibility by surfacing invalid mermaid syntax.

💡 Performance: Viewport refits on every scene update, resetting pan/zoom

📄 desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx:94-100

The effect calls api.updateScene(...) and then api.scrollToContent(..., { fitToContent: true }) every time sceneElements changes. Because diagrams resolve asynchronously, each diagram conversion (and any change to the elements prop) re-fits the viewport, snapping the view back to fit-content even if the user has panned/zoomed. With multiple diagrams resolving one-by-one (see sequential loop), the board will visibly jump several times on load.

Consider running scrollToContent only once (e.g. the first time api becomes available and the scene is non-empty), and using a ref/flag to avoid re-fitting on subsequent async scene pushes. While the board is read-only today, the refit-on-every-update behavior will be disruptive once interactions land in later slices.

💡 Quality: Sequential awaits make multi-diagram conversion slow

📄 desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx:60-67

The conversion loop awaits mermaidToExcalidraw for each diagram element serially inside a for loop, so total latency is the sum of all conversions. mermaid parsing is comparatively heavy; with several diagrams on a board the placeholder rectangles will persist noticeably longer than necessary. Each conversion is independent, so Promise.all(diagramEls.map(...)) would parallelize them and shorten time-to-render. If you prefer incremental display, set state per-element as each resolves instead of building one next map at the end.

💡 Quality: Invalid mermaid is swallowed silently with no diagnostics

📄 desktop/src/apps/ProjectsApp/canvas/mermaid-to-elements.ts:26-28

catch { return []; } discards the parse error entirely. Degrading to the placeholder rectangle is the intended UX, but with no console.warn/debug logging a malformed mermaid source becomes invisible to developers and users alike — the diagram just silently stays a placeholder with no indication why. Consider logging the caught error (at least in dev) so authoring mistakes in the source can be diagnosed.

🤖 Prompt for agents
Code Review: Integrates real mermaid diagram rendering by converting sources into Excalidraw elements. Address the viewport reset bug on scene updates, optimize conversion latency by parallelizing awaits, and improve error visibility by surfacing invalid mermaid syntax.

1. 💡 Performance: Viewport refits on every scene update, resetting pan/zoom
   Files: desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx:94-100

   The effect calls `api.updateScene(...)` and then `api.scrollToContent(..., { fitToContent: true })` every time `sceneElements` changes. Because diagrams resolve asynchronously, each diagram conversion (and any change to the `elements` prop) re-fits the viewport, snapping the view back to fit-content even if the user has panned/zoomed. With multiple diagrams resolving one-by-one (see sequential loop), the board will visibly jump several times on load.
   
   Consider running `scrollToContent` only once (e.g. the first time `api` becomes available and the scene is non-empty), and using a ref/flag to avoid re-fitting on subsequent async scene pushes. While the board is read-only today, the refit-on-every-update behavior will be disruptive once interactions land in later slices.

2. 💡 Quality: Sequential awaits make multi-diagram conversion slow
   Files: desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx:60-67

   The conversion loop awaits `mermaidToExcalidraw` for each diagram element serially inside a `for` loop, so total latency is the sum of all conversions. mermaid parsing is comparatively heavy; with several diagrams on a board the placeholder rectangles will persist noticeably longer than necessary. Each conversion is independent, so `Promise.all(diagramEls.map(...))` would parallelize them and shorten time-to-render. If you prefer incremental display, set state per-element as each resolves instead of building one `next` map at the end.

3. 💡 Quality: Invalid mermaid is swallowed silently with no diagnostics
   Files: desktop/src/apps/ProjectsApp/canvas/mermaid-to-elements.ts:26-28

   `catch { return []; }` discards the parse error entirely. Degrading to the placeholder rectangle is the intended UX, but with no `console.warn`/debug logging a malformed mermaid source becomes invisible to developers and users alike — the diagram just silently stays a placeholder with no indication why. Consider logging the caught error (at least in dev) so authoring mistakes in the source can be diagnosed.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Important

Your trial ends in 3 days — upgrade now to keep code review, CI analysis, auto-apply, custom automations, and more.

Was this helpful? React with 👍 / 👎 | Gitar

@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: 1

🧹 Nitpick comments (1)
desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx (1)

60-65: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Run diagram conversions in parallel to reduce placeholder latency.

Lines 62–65 process each diagram serially. For multiple diagrams, total wait time becomes the sum of all parse times.

⚡ Proposed refactor
     (async () => {
-      const next: Record<string, ExcalidrawElements> = {};
-      for (const el of diagramEls) {
-        const src = String((el.payload || {}).source ?? "");
-        next[el.id] = await mermaidToExcalidraw(src, el.x, el.y);
-      }
+      const entries = await Promise.all(
+        diagramEls.map(async (el) => {
+          const src = String((el.payload || {}).source ?? "");
+          const converted = await mermaidToExcalidraw(src, el.x, el.y);
+          return [el.id, converted] as const;
+        }),
+      );
+      const next: Record<string, ExcalidrawElements> = Object.fromEntries(entries);
       if (!cancelled) setDiagrams(next);
     })();
🤖 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 `@desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx` around lines 60 -
65, The for loop iterating over diagramEls currently processes each diagram
conversion sequentially with await, causing total latency to be the sum of all
conversions. Replace the sequential for loop with a parallel approach by using
Promise.all() to create all mermaidToExcalidraw promises simultaneously, then
await them all at once. Map each el from diagramEls to a mermaidToExcalidraw
call, resolve all promises in parallel, and then populate the next object with
the results in a single operation to significantly reduce the total wait time
for multiple diagrams.
🤖 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 `@desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx`:
- Around line 75-90: The current implementation in the sceneElements useMemo
hook separates elements by kind (regular vs diagram) into separate arrays and
then concatenates them, which loses the original z-index ordering. To preserve
cross-kind z-order, iterate through the original elements array in its original
order, and for each element, check if it is a diagram kind using DIAGRAM_KINDS
and convert it appropriately (using the diagram from the diagrams mapping if
available, or the elementToSkeleton fallback). This way, elements maintain their
intended stacking order regardless of their kind. The result should interleave
regularScene and diagramScene elements based on the original elements array
ordering instead of concatenating all regular elements first followed by all
diagram elements.

---

Nitpick comments:
In `@desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx`:
- Around line 60-65: The for loop iterating over diagramEls currently processes
each diagram conversion sequentially with await, causing total latency to be the
sum of all conversions. Replace the sequential for loop with a parallel approach
by using Promise.all() to create all mermaidToExcalidraw promises
simultaneously, then await them all at once. Map each el from diagramEls to a
mermaidToExcalidraw call, resolve all promises in parallel, and then populate
the next object with the results in a single operation to significantly reduce
the total wait time for multiple diagrams.
🪄 Autofix (Beta)

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d1e95a73-7b0e-4dff-ba77-4d36170ff033

📥 Commits

Reviewing files that changed from the base of the PR and between 7e093ab and 5af005a.

⛔ Files ignored due to path filters (1)
  • desktop/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (5)
  • desktop/package.json
  • desktop/src/apps/ProjectsApp/__tests__/ExcalidrawBoard.test.tsx
  • desktop/src/apps/ProjectsApp/__tests__/mermaid-to-elements.test.ts
  • desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx
  • desktop/src/apps/ProjectsApp/canvas/mermaid-to-elements.ts

Comment thread desktop/src/apps/ProjectsApp/canvas/ExcalidrawBoard.tsx
…mermaid errors

- Convert mermaid/flowchart diagrams in parallel (Promise.all) so total latency
  is the slowest single diagram, not the sum.
- Fit the viewport to content only on the first non-empty scene, so a later
  scene update (async diagrams, and write-back once interactions land) no longer
  resets the user's pan/zoom.
- Log a swallowed mermaid parse error instead of discarding it silently, while
  still degrading to the placeholder.

Re-verified the flowchart still renders after these changes.
useEffect(() => {
if (!api) return;
api.updateScene({ elements: sceneElements });
if (!hasFit.current && sceneElements.length > 0) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

SUGGESTION: Fit-once can lock the viewport to the placeholder extent

On mount, sceneElements is built from skeletons + placeholder rectangles for diagrams that are still being converted asynchronously. If any element exists, this effect runs scrollToContent against the placeholder scene and flips hasFit.current to true. When the async conversion later resolves and replaces placeholders with the real (often larger) mermaid diagram, hasFit.current is now true, so the viewport never re-fits to the larger content. The user sees a zoom/pan centered on the placeholder rectangle while the actual diagram extends beyond the viewport.

Consider fitting on the first non-empty scene after diagrams have resolved (e.g. gate on diagrams being populated, or re-fit whenever diagrams transitions from empty → non-empty even after the initial fit).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Composing the scene as [all regular, then all diagrams] forced every diagram
above every regular element regardless of z_index. Walk the elements in z_index
order instead (live() already sorts them) and map each in place, so a diagram
with a low z_index can sit below a regular element with a higher one.
@jaylfc jaylfc merged commit 4d938a8 into dev Jun 23, 2026
8 checks passed
@github-project-automation github-project-automation Bot moved this from Todo to Done in TinyAgentOS Roadmap Jun 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

1 participant