Skip to content

fix: person shape renders as ellipse in Draw.io export#2682

Merged
davydkov merged 4 commits intomainfrom
drawio-person-shape
Mar 6, 2026
Merged

fix: person shape renders as ellipse in Draw.io export#2682
davydkov merged 4 commits intomainfrom
drawio-person-shape

Conversation

@davydkov
Copy link
Copy Markdown
Member

@davydkov davydkov commented Mar 4, 2026

Summary

Fixes #2679: Elements with shape person now render correctly in Draw.io exports. Changed from shape=umlActor to shape=actor.

The umlActor shape is a stick figure inside an ellipse boundary. When combined with HTML content rendering (overflow=fill;html=1;), the HTML fills the ellipse and hides the figure, leaving only the ellipse visible. The built-in actor shape is a person silhouette that renders correctly with HTML content.

Checklist

  • Change is minimal and focused on the bug fix
  • Tests pass (pnpm vitest run packages/generators/src/drawio/generate-drawio.spec.ts)
  • Commit follows conventional spec

Summary by CodeRabbit

  • Bug Fixes
    • DrawIO exports: person nodes now render as actor/person shapes instead of ellipses, restoring expected visuals in exported diagrams.
    • Actor rendering simplified by removing incorrect label-position modifiers, improving layout and readability.
    • Shapes using the actor style (including explicit actor styling) are now consistently recognized and displayed as actors for uniform diagram output.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 4, 2026

🦋 Changeset detected

Latest commit: a83602d

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 19 packages
Name Type
@likec4/generators Patch
@likec4/playground Patch
@likec4/language-services Patch
likec4 Patch
@likec4/vite-plugin Patch
@likec4/mcp Patch
likec4-vscode Patch
@likec4/docs-astro Patch
@likec4/style-preset Patch
@likec4/styles Patch
@likec4/config Patch
@likec4/core Patch
@likec4/diagram Patch
@likec4/language-server Patch
@likec4/layouts Patch
@likec4/log Patch
@likec4/react Patch
@likec4/tsconfig Patch
@likec4/vscode-preview Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 4, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 04cfab05-8a5a-4326-8701-35b2be092766

📥 Commits

Reviewing files that changed from the base of the PR and between 3eda7ea and a83602d.

📒 Files selected for processing (3)
  • .changeset/fix-drawio-person-shape.md
  • packages/generators/src/drawio/generate-drawio.ts
  • packages/generators/src/drawio/parse-drawio.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/generators/src/drawio/parse-drawio.ts
  • packages/generators/src/drawio/generate-drawio.ts

📝 Walkthrough

Walkthrough

Changed DrawIO handling: map node shape person to shape=actor;, detect shape=actor as an actor in the parser, and make a minor quote-escaping tweak in findOpenTagEnd.

Changes

Cohort / File(s) Summary
DrawIO Shape Mapping
packages/generators/src/drawio/generate-drawio.ts
Replaced UML-style actor string (with label-position modifiers) for node shape person with a simple DrawIO actor shape string (shape=actor;) to ensure actor rendering.
DrawIO Parser
packages/generators/src/drawio/parse-drawio.ts
Adjusted quote escaping in findOpenTagEnd checks and extended inferKind to treat shape=actor as actor alongside existing shape=person / umlactor detections.
Release / Changeset
.changeset/fix-drawio-person-shape.md
Added changeset markdown documenting a patch release and the bugfix for person shapes rendering as ellipses in DrawIO exports.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

I’m a rabbit with nimble paws,
I nudged a string and smoothed the draws.
No more ellipses hiding the actor,
A tiny hop, the diagram’s truer. 🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: fixing person shape rendering in Draw.io exports to display as an actor instead of an ellipse.
Description check ✅ Passed The description covers the issue fix, explains the root cause, and documents the technical change with test verification, though the checklist template was not fully followed.
Linked Issues check ✅ Passed The PR successfully addresses issue #2679 by changing the Draw.io shape mapping from umlActor to actor, ensuring person-shaped elements render correctly instead of as ellipses.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the person shape rendering issue; the quote escaping in parse-drawio.ts appears to support the actor shape detection logic.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch drawio-person-shape

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/generators/src/drawio/generate-drawio.ts`:
- Around line 238-239: The inferKind function in parse-drawio.ts currently only
matches 'umlactor' or 'shape=person' when determining an actor shape; update the
pattern used in inferKind to also recognize 'shape=actor' so exported Draw.io
shapes round-trip correctly (i.e., add 'shape=actor' to the existing matcher
that checks for 'umlactor' or 'shape=person' so it returns 'actor' instead of
falling through to 'container').

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 68b5f515-f6a1-4eb7-aa3b-5f752b329702

📥 Commits

Reviewing files that changed from the base of the PR and between 27e4c47 and 5eddf7a.

📒 Files selected for processing (1)
  • packages/generators/src/drawio/generate-drawio.ts

@davydkov davydkov requested a review from sraphaz March 4, 2026 09:56
@davydkov
Copy link
Copy Markdown
Member Author

davydkov commented Mar 4, 2026

@sraphaz Hey Raphael, can you review this one?

davydkov and others added 4 commits March 4, 2026 11:17
…export

The umlActor shape renders as a stick figure inside an ellipse boundary.
When combined with HTML content rendering (overflow=fill;html=1;), the
HTML content fills the ellipse and hides the stick figure, leaving only
the ellipse visible. The actor shape is a built-in mxGraph person
silhouette that renders correctly with HTML content.

Fixes #2679

Co-Authored-By: Claude Haiku 4.5 <[email protected]>
The inferKind function only matched umlactor and shape=person. After
changing the export to use shape=actor, re-imported files would not
recognize the person shape correctly.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
@davydkov davydkov force-pushed the drawio-person-shape branch from 3eda7ea to a83602d Compare March 4, 2026 10:17
@sraphaz
Copy link
Copy Markdown
Collaborator

sraphaz commented Mar 4, 2026

Review: fix: person shape renders as ellipse in Draw.io export (#2682)

We contributed the initial Draw.io export/import feature to LikeC4 and were asked to review this PR. We take that responsibility seriously and have reviewed the change with care for correctness, round-trip integrity, and alignment with the project’s guidelines.


🌟 Recognition

Thank you for addressing #2679 and for the clear PR description and commit messages.

What this PR does: Elements with shape person in LikeC4 were exporting to Draw.io as shape=umlActor. The UML actor shape is a stick figure inside an ellipse; with our export’s HTML content rendering (overflow=fill;html=1;), the HTML fills the ellipse and hides the figure, so users only saw an ellipse. This PR switches the export to Draw.io’s built-in shape=actor (a person silhouette that works correctly with HTML content) and updates the parser so that re-imported diagrams using shape=actor are still recognized as actors. The fix is minimal, well-scoped, and includes the quote-handling improvement in findOpenTagEnd for robustness.

We recognize the care put into the implementation, the explanation of the root cause, and the inclusion of the parser change so that export and import stay in sync.


🔍 Our view on the integrity of the solution

From the perspective of maintainability and round-trip behavior:

Correctness: The change fixes the reported bug. Export now produces a shape that renders as intended in Draw.io, and import still classifies those cells as actor, so behavior is correct.
Minimal scope: Only the person/actor path is touched; the rest of the Draw.io pipeline is unchanged. This matches the repo’s preference for focused, single-purpose changes.
Round-trip behavior: After this PR, “export person → edit in Draw.io → re-import” yields name = actor 'title' as expected. Semantically, the round-trip is already correct because inferKind returns actor for shape=actor.
Tests and snapshots: Running pnpm vitest run packages/generators/src/drawio/generate-drawio.spec.ts (and the parse-drawio specs) is appropriate; any snapshots that include actor/person elements should be updated to expect shape=actor instead of shape=umlActor in the generated XML.

🎉 We consider the solution sound and ready to merge from a correctness and integrity standpoint.


⚙️ Technical review

Export (generate-drawio.ts)
Replacing shape=umlActor;verticalLabelPosition=bottom;verticalAlign=top; with shape=actor; for the person shape is the right fix. The simpler style string avoids the ellipse-vs-HTML conflict and matches Draw.io’s recommended shape for this use case. Dropping the label-position modifiers is consistent with the new shape.

Import (parse-drawio.ts)
Extending inferKind so that shape=actor is recognized (e.g. s.includes('umlactor') || s.includes('shape=person') || s.includes('shape=actor')) ensures that cells exported with the new format are still classified as actor on re-import. Without this, they would fall through to container. The change is in the right place and keeps export and import aligned.

Quote handling in findOpenTagEnd
Handling both single- and double-quoted attributes so that a > inside a quoted value does not end the tag improves robustness and avoids misparsing in edge cases. This is a solid, defensive improvement.

Conventions
The change follows the repo’s TypeScript style, keeps the change local to the person/actor path, and fits the existing structure of drawioShape and inferKind. Commit messages follow the conventional style used in the project.


💡 Optional enhancement: explicit shape person on re-import (round-trip fidelity)

We support merging this PR as-is. If you are open to a small, optional improvement for round-trip fidelity, we suggest the following.

Current behavior after this PR:

  • Export: personshape=actor.
  • Re-import: shape=actorinferKindactor → emitted as name = actor 'title' (and optionally a body). The element is correct; in LikeC4, actor implies the person shape by default.

Possible improvement:
Today, inferShape() in parse-drawio.ts only returns 'cylinder', 'document', or 'rectangle'. It does not return 'person' for actor-style cells. So when we re-import a .drawio that contains an actor (with shape=actor), we do not emit an explicit style { shape person } in the .c4 source. The diagram still renders correctly because the default for actor is the person shape, but the source would be more faithful to the diagram if we emitted the shape explicitly.

Suggestion: Extend inferShape() to return 'person' when the cell’s style indicates an actor shape, so that re-imported actor cells get an explicit style { shape person } in the emitted .c4. That would make the round-trip symmetric: export person → shape=actor; import shape=actor → actor with explicit shape person.

Concrete change (optional): In inferShape() in parse-drawio.ts, add a branch before the existing shape checks, for example:

function inferShape(style: string | undefined): string | undefined {
  if (!style) return undefined
  const s = style.toLowerCase()
  // Actor/person shape (export uses shape=actor; legacy may have shape=person or umlactor)
  if (s.includes('shape=actor') || s.includes('shape=person') || s.includes('umlactor')) return 'person'
  if (s.includes('shape=cylinder') || s.includes('cylinder3')) return 'cylinder'
  // ... rest unchanged
}

Then pushElementStyleBlock would emit shape person for such cells when building the element body, and the generated .c4 would explicitly reflect the shape. This is purely additive and does not change semantics; it only makes the round-trip more explicit. If you prefer to keep this PR minimal, we are happy to propose this as a small follow-up PR after merge.


🎁 Responsibility and care

In this review we aimed to:

  • Verify correctness: Confirm that the fix resolves the issue and does not introduce regressions in export or import.
  • Check round-trip integrity: Ensure that export and import stay aligned so that “export → edit in Draw.io → re-import” preserves intent.
  • Respect project norms: Focused change, clear commits, tests run, and no unnecessary scope creep.
  • Propose improvements only where useful: The optional inferShape suggestion is for extra fidelity, not a requirement for approval.

We are satisfied with the solution and recommend merging. We are grateful for the fix and for the opportunity to review; we are happy to follow up with a small PR for the optional inferShape enhancement if that would be helpful.

sraphaz added a commit to sraphaz/likec4 that referenced this pull request Mar 4, 2026
inferKind() now includes shape=actor so vertex with shape=actor is emitted as actor (not container). Aligns with upstream likec4#2682 and makes this branch pass ci:test without depending on likec4#2682 merge.

Made-with: Cursor
Copy link
Copy Markdown
Collaborator

@sraphaz sraphaz left a comment

Choose a reason for hiding this comment

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

LGTM. We (contributors of the initial Draw.io export/import) reviewed this PR in detail in an earlier comment. The fix is correct: switching export to shape=actor and recognizing it in inferKind restores proper person rendering in Draw.io and keeps round-trip consistent. Optional follow-up (explicit shape person on re-import) is addressed in our PR #2685. Approving.

@sraphaz sraphaz self-assigned this Mar 4, 2026
@davydkov davydkov merged commit aab9343 into main Mar 6, 2026
15 checks passed
@davydkov davydkov deleted the drawio-person-shape branch March 6, 2026 08:37
davydkov pushed a commit that referenced this pull request Mar 6, 2026
…#2685)

* feat(drawio): infer shape person on re-import for round-trip fidelity

inferShape() now returns 'person' when DrawIO cell style contains shape=actor, shape=person, or umlactor. Re-imported actor cells get explicit style { shape person } in emitted .c4.

Made-with: Cursor

* fix(drawio): recognize shape=actor in inferKind for CI/self-contained PR

inferKind() now includes shape=actor so vertex with shape=actor is emitted as actor (not container). Aligns with upstream #2682 and makes this branch pass ci:test without depending on #2682 merge.

Made-with: Cursor

* docs(drawio): align changeset and comment with current exporter (shape=actor or umlActor)

CodeRabbit: changeset now states export may emit shape=actor or shape=umlActor; inferShape comment updated to match.
Made-with: Cursor
@likec4-ci likec4-ci bot mentioned this pull request Mar 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Draw.io export of elements with shape "person" results in ellipse shape

2 participants