Skip to content

Revert "feat(cli,playground,docs,generators): Export LikeC4 views to Draw.io"#2629

Merged
sraphaz merged 1 commit intomainfrom
revert-2622-main
Feb 14, 2026
Merged

Revert "feat(cli,playground,docs,generators): Export LikeC4 views to Draw.io"#2629
sraphaz merged 1 commit intomainfrom
revert-2622-main

Conversation

@sraphaz
Copy link
Copy Markdown
Collaborator

@sraphaz sraphaz commented Feb 14, 2026

Reverts #2622

Summary by CodeRabbit

  • New Features

    • Added DrawIO import capability to convert .drawio diagrams to LikeC4 format via CLI command
  • Bug Fixes

    • Simplified DrawIO export functionality for improved stability
  • Documentation

    • Updated CLI tooling documentation with new import examples and consolidated export guidance
    • Added beginner-oriented setup notes for project contributors
  • Chores

    • Updated workflow runners to latest ubuntu image
    • Streamlined package post-build process

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Feb 14, 2026

⚠️ No Changeset found

Latest commit: c6a872f

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

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

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@sraphaz sraphaz merged commit 654240a into main Feb 14, 2026
14 of 16 checks passed
@sraphaz sraphaz deleted the revert-2622-main branch February 14, 2026 14:34
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Feb 14, 2026

📝 Walkthrough

Walkthrough

This PR removes Draw.io import/export functionality across the codebase, replaces the likec4ops postpack command with direct file copying in package configurations, simplifies CLI handlers, refactors playground components, removes E2E tests, and updates documentation and workflows accordingly.

Changes

Cohort / File(s) Summary
Draw.io Generator & Parser Modules
packages/generators/src/drawio/generate-drawio.ts, packages/generators/src/drawio/parse-drawio.ts, packages/generators/src/drawio/constants.ts, packages/generators/src/drawio/index.ts
Removed comprehensive Draw.io export/import infrastructure: replaced multi-option generateDrawio API with simplified single-parameter version, removed DrawioViewModelLike and GenerateDrawioOptions types, eliminated 64 constants, and drastically simplified XML parsing from multi-pass feature-rich implementation to single-pass regex-based extraction.
Draw.io Test Files
packages/generators/src/drawio/generate-drawio.spec.ts, packages/generators/src/drawio/parse-drawio.spec.ts, packages/likec4/src/drawio-demo-export-import.spec.ts, packages/likec4/src/drawio-tutorial-export-import.spec.ts
Removed or significantly simplified test suites for Draw.io export/import functionality, reducing test coverage and eliminating snapshot-based and roundtrip validation tests.
CLI Export/Import Handlers
packages/likec4/src/cli/export/drawio/handler.ts, packages/likec4/src/cli/import/drawio/handler.ts, packages/likec4/src/cli/import/index.ts, packages/likec4/src/cli/index.ts
Added new Draw.io import CLI command and refactored export handler with simplified per-view processing; updated CLI wiring to include import command and adjusted error handling.
Playground DrawIO Components
apps/playground/src/components/drawio/DrawioContextMenu.tsx, apps/playground/src/components/drawio/DrawioContextMenuDropdown.tsx, apps/playground/src/components/drawio/DrawioContextMenuProvider.tsx, apps/playground/src/components/drawio/DrawioContextMenuView.tsx, apps/playground/src/components/drawio/useDrawioActions.ts, apps/playground/src/components/drawio/useDrawioContextMenuActions.ts
Removed context menu dropdown component, replaced complex action orchestration (useDrawioContextMenuActions) with simpler hook-based approach (useDrawioActions), simplified provider props and API surface.
Playground Monaco Editor & Layout
apps/playground/src/monaco/LanguageClientSync.tsx, apps/playground/src/monaco/MonacoEditor.tsx, apps/playground/src/monaco/index.tsx, apps/playground/src/routes/w.$workspaceId/route.tsx
Removed layoutedModelApi prop threading throughout editor components, simplified component signatures to require no props, consolidated layout handling logic.
Package.json Postpack Scripts
packages/config/package.json, packages/core/package.json, packages/diagram/package.json, packages/generators/package.json, packages/icons/package.json, packages/language-server/package.json, packages/language-services/package.json, packages/layouts/package.json, packages/likec4/package.json, packages/log/package.json, packages/mcp/package.json, styled-system/preset/package.json, styled-system/styles/package.json
Replaced likec4ops postpack command with direct file copy pattern (cp likec4-<pkg>-$npm_package_version.tgz package.tgz \|\| true) across all workspace packages; removed postpack scripts from vite-plugin package.
DevOps & Build
devops/commands/postpack.ts, devops/likec4ops.ts, apps/playground/scripts/generate.mts
Removed postpack command module and CLI integration; simplified playground generate script by removing Windows PowerShell handling and adding .env initialization logic.
GitHub Workflows
.github/workflows/ci-pr.yaml, .github/deprecated/auto-assign.yaml, .github/workflows/cleanup-cache.yml, .github/workflows/codeql.yml, .github/workflows/copilot-setup-steps.yml, .github/workflows/test-export-action.yml, .github/workflows/trigger-deploy-template.yaml
Removed entire CI PR workflow, updated runner from ubuntu-24.04 to ubuntu-latest across multiple workflows.
E2E Tests & Configuration
e2e/tests/drawio-playground.spec.ts, e2e/tests/docs-smoke.spec.ts, e2e/tests/static-navigation.spec.ts, e2e/src/likec4-cli-export-drawio.spec.ts, e2e/E2E-COVERAGE.md, e2e/.gitignore, e2e/playwright.config.ts, e2e/playwright.playground.config.ts, e2e/helpers/selectors.ts, e2e/helpers/timeouts.ts, e2e/package.json
Removed all Draw.io-related E2E tests, playground-specific Playwright config, smoke tests for docs, and navigation tests; deleted shared selectors/timeout helpers; removed test:playground script and testIgnore/maxDiffPixelRatio from Playwright config.
Language Server
packages/language-server/src/browser/worker.ts, packages/language-server/src/browser/index.ts, packages/language-server/src/documentation/documentation-provider.ts, packages/language-server/src/lsp/HoverProvider.ts, packages/language-server/src/model/last-seen-artifacts.ts, packages/language-server/src/model/model-locator.ts, packages/language-server/src/utils/projectId.ts, packages/language-server/CHANGELOG.md
Simplified error handling in worker (removed try/catch wrapper, added worker global scope declaration), added cache invalidation and deduplication in artifact caching, removed numerous JSDoc comments from documentation/hover/model helpers.
Language Services
packages/language-services/src/node/index.ts, packages/language-services/src/node/without-mcp/index.ts
Simplified workspace URI construction by removing intermediate variables and direct assignment via pathToFileURL.
Logging & Utilities
packages/log/src/formatters.ts, packages/log/src/index.ts, packages/log/src/sink.ts, packages/log/src/utils.ts, packages/likec4/src/logger.ts
Removed debug method from logger, simplified sink composition in configureLogger, removed loggable helper usage, removed JSDoc comments; updated ViteLogger type and hasErrorLogged method signature.
CLI & Export Handlers
packages/likec4/src/cli/export/json/handler.ts, packages/likec4/src/cli/export/png/handler.ts, packages/likec4/src/cli/export/png/takeScreenshot.ts, packages/likec4/src/cli/export/index.ts
Inlined export workflows directly into handlers, removed intermediate wrapper functions, replaced LikeC4.fromWorkspace with fromWorkspace import, simplified error handling and logging.
Config & Utilities
packages/config/src/filenames.ts, packages/generators/src/drawio/xml-utils.ts, packages/generators/src/index.ts, packages/generators/package.json, packages/vite-plugin/src/virtuals/icons.ts
Added pathe import for basename extraction, removed export of DrawIO constants and multi-function API, removed XML utility functions, simplified icon registry generation by removing validation logic and helper functions, removed pako dependency.
Documentation
apps/docs/src/content/docs/tooling/cli.mdx, apps/docs/src/content/docs/tooling/drawio.mdx, apps/docs/src/content/docs/tooling/docker.mdx, apps/playground/README.md, BEGINNER_NONTES.md
Deleted dedicated Draw.io documentation page, moved import/export guidance to CLI docs, added beginner setup notes, updated docker example, removed playground troubleshooting section.
Changesets
.changeset/drawio-cli-roundtrip-e2e.md, .changeset/drawio-import-postpack.md, .changeset/drawio-roundtrip-options.md
Removed changelog entries documenting Draw.io CLI features, roundtrip functionality, and postpack behavior.
Miscellaneous Config
.gitignore, packages/diagram/src/likec4diagram/ui/notation/NotationPanel.tsx, packages/language-server/src/model/builder/MergedSpecification.ts, packages/vscode/src/node/mcp-server.ts, packages/vscode/src/node/useMcpRegistration.ts, packages/vscode/src/useWorkspaceId.ts, packages/vscode/src/utils.ts, packages/vscode/tsdown.config.ts
Removed local PR/docs entries from gitignore, added empty-state UI for NotationPanel, simplified error handling in VS Code components and MCP registration, removed JSDoc comments from utility functions, added workspace ID persistence logic (partially commented).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • davydkov

Poem

🐰 A rabbit hops through code with glee,
Removing draws and tearing free,
The Draw.io threads both high and low,
Simpler paths now start to flow,
With postpack replaced by humble cp,
This cleanup fills my heart with glee! 🎉

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.43% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: reverting a feature commit that added Draw.io export functionality.
Description check ✅ Passed The description is minimal but sufficient for a revert PR, clearly referencing the issue being reverted (PR #2622).
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch revert-2622-main

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: 12

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 (2)
apps/playground/scripts/generate.mts (1)

8-12: ⚠️ Potential issue | 🟡 Minor

Hardcoded SESSION_ENCRYPTION_KEY in source control.

Even for a local-dev bootstrap, committing a secret value to VCS means it'll persist in git history. If this key is only a placeholder for local development, consider noting that explicitly with a comment, or generating a random value at script runtime instead.

packages/config/src/filenames.ts (1)

30-35: ⚠️ Potential issue | 🟡 Minor

JSDoc comment is incomplete — missing CJS and CTS.

The comment lists (JS, MJS, TS, MTS) but the array also includes .cjs and .cts.

📝 Proposed fix
-/**
- * Checks if the given filename is a LikeC4 non-JSON config file (JS, MJS, TS, MTS)
- */
+/**
+ * Checks if the given filename is a LikeC4 non-JSON config file (JS, CJS, MJS, TS, CTS, MTS)
+ */
🤖 Fix all issues with AI agents
In `@BEGINNER_NONTES.md`:
- Around line 1-75: The file BEGINNER_NONTES.md appears to be accidental
developer notes — either remove it from the PR or clean and relocate it; if
keeping it, rename the file to BEGINNER_NOTES.md, fix title "Beginner Nontes" →
"Beginner Notes", correct typos "Ubluntu" → "Ubuntu" and "Avodi pnmp" → "Avoid
pnpm", remove the stray literal "shell" inside the code block and change the
fence to ```shell, remove the trailing backtick in the pnpm turbo command, and
add language specifiers to all fenced code blocks (e.g., ```bash or ```shell);
consider moving the cleaned content into CONTRIBUTING.md or project wiki instead
of this PR.

In `@packages/diagram/src/likec4diagram/ui/notation/NotationPanel.tsx`:
- Around line 140-161: Restore the original revert behavior by removing the new
empty-state UI block: delete the conditional JSX that renders the <m.div
key={'empty'} ...> containing Tooltip, ThemeIcon and IconAlertTriangle when
!hasNotations, and instead reinstate the early-return pattern (if
(!hasNotations) return null) in NotationPanel (where hasNotations is computed)
so the component renders nothing when there are no notations; if an empty-state
is desired, move that JSX into a separate PR/component rather than keeping it in
this revert.

In `@packages/generators/src/drawio/generate-drawio.ts`:
- Around line 160-162: The current code builds userData as an <mxUserObject>
string and then injects it as a child inside the existing <mxCell>, which is
incorrect; instead, when description/technology exist (userData non-empty) wrap
the entire cell with an outer <mxUserObject> (or <UserObject>), put the <mxCell>
as its child, and place the description/technology <data> entries inside that
outer element; locate the assembly logic that builds userData and the place
where the <mxCell> is emitted (symbols: userData, mxUserObject, mxCell in
generate-drawio.ts) and change the output composition so that if userData is
non-empty you emit <mxUserObject>…<mxCell>…</mxCell></mxUserObject> rather than
embedding <mxUserObject> inside the <mxCell>.
- Line 210: The MXGraphModel element in the drawio XML output contains an
unquoted attribute pageScale=1 which produces invalid XML; update the
string/template that emits the MXGraphModel (look for the literal '<mxGraphModel
dx="800" ... pageScale=1 pageWidth="827" ...' in generate-drawio.ts) to quote
the value as pageScale="1" so all attributes are properly quoted and the
generated XML is well-formed.

In `@packages/generators/src/drawio/parse-drawio.ts`:
- Around line 56-63: parseUserData currently only searches for <data
key="likec4Description"> inside the mxCell inner content so it misses standard
Draw.io custom data which is stored in a wrapping <UserObject> or <object>;
update parseUserData to also detect and parse <UserObject> or <object> elements
that wrap an <mxCell> (extract their <data key="likec4Description"> and <data
key="likec4Technology">), and update the generator in generate-drawio (where
mxUserObject is currently nested inside mxCell) to emit the
mxUserObject/UserObject/object as a sibling/wrapper of <mxCell> consistent with
Draw.io export so round-trips preserve description and technology.
- Around line 159-173: The byId Map is built from all cells but never used;
remove the unused variable and its population loop (the const byId = new
Map<string, DrawioCell>() and the for (const c of cells) { byId.set(c.id, c) }
block) and keep idToCell (which is populated from vertices) as the authoritative
mapping; verify there are no remaining references to byId and ensure idToCell
continues to be used where vertex lookups occur (symbols: byId, idToCell,
DrawioCell, vertices).

In `@packages/language-server/src/model/model-locator.ts`:
- Around line 62-63: The local variables astNodeOrFqn and projectId in the
locateModel function are declared without types; add explicit type annotations
to remove implicit any by matching the function's overload signature—annotate
astNodeOrFqn with the same union/type used for the function's first overload
parameter (e.g., string | <AST node type>) and annotate projectId with the
overload's projectId type (e.g., string | undefined); update the declarations
for astNodeOrFqn and projectId in model-locator.ts accordingly.

In `@packages/likec4/src/cli/export/drawio/handler.ts`:
- Around line 43-46: The current call to fromWorkspace uses the
option-definition object useDotBin in the ternary (useDotBin ? 'binary' :
'wasm'), which is always truthy; update the graphviz selection to use the parsed
CLI value args['use-dot'] instead (e.g., graphviz: args['use-dot'] ? 'binary' :
'wasm') in the fromWorkspace invocation inside the handler (the block that
constructs likec4), leaving the imported useDotBin option definition unchanged.

In `@packages/likec4/src/cli/export/json/handler.ts`:
- Around line 52-55: The graphviz selection is using the imported option
definition useDotBin (always truthy) instead of the CLI value, so update the
call that creates languageServices (the fromWorkspace invocation that assigns
languageServices) to read the actual flag from args (use args['use-dot']) and
set graphviz: args['use-dot'] ? 'binary' : 'wasm'; keep the rest of the options
(watch: false) unchanged and ensure you reference the same symbols
(fromWorkspace, languageServices, graphviz) so the change is localized.

In `@packages/likec4/src/cli/export/png/handler.ts`:
- Around line 152-165: The Vite dev server started when serverUrl is falsy (via
viteDev in the server variable) may never be closed if the export loop
(pngHandler) throws; wrap the export loop and subsequent cleanup in a
try/finally so server.close() always runs. Concretely, around the code that
calls pngHandler (and any export loop that can throw), introduce try { /* export
logic */ } finally { if (server) await server.close(); } — reference the server
variable, viteDev(), resolveServerUrl(), and the pngHandler call so you close
the started server regardless of exceptions.

In `@packages/likec4/src/cli/index.ts`:
- Around line 94-96: The current catch on main() swallows the thrown error;
update the catch handler for main() to accept the error (e.g., err) and log it
before calling exit(1). Locate the invocation main().catch(() => { exit(1) })
and change it to a catch that prints the error (using console.error or
process.stderr) with a brief context message and then calls exit(1), so failures
like invalid workspace paths are visible to the user.

In `@packages/likec4/src/logger.ts`:
- Around line 49-51: The hasErrorLogged function currently throws an Error at
runtime; change its implementation to be a no-op that returns false instead.
Locate the hasErrorLogged method in logger.ts and replace the throw new
Error('Function not implemented.') with a simple return false so the function
satisfies Vite's logger interface without crashing during error deduplication.
🟡 Minor comments (12)
packages/language-server/CHANGELOG.md-19-19 (1)

19-19: ⚠️ Potential issue | 🟡 Minor

Add a language tag to the fenced code block.

MD040 is triggered because the opening fence lacks a language. Consider restoring likec4 (or text) for proper rendering.

💡 Suggested fix
-  ```
+  ```likec4
packages/log/src/formatters.ts-49-58 (1)

49-58: ⚠️ Potential issue | 🟡 Minor

Typo in variable name: errorMessgeerrorMessage.

Used consistently across lines 49–58 so there's no runtime breakage, but it's clearly a misspelling.

Proposed fix
-    let errorMessge = error.message
+    let errorMessage = error.message
     if (error.stack) {
-      errorMessge = errorMessge + '\n' + indent(error.stack.split('\n').slice(1))
+      errorMessage = errorMessage + '\n' + indent(error.stack.split('\n').slice(1))
     }
     if (color) {
-      errorMessge = `${ansiColors.red}${errorMessge}${RESET}`
+      errorMessage = `${ansiColors.red}${errorMessage}${RESET}`
     }
     return {
       ...values,
-      message: values.message + '\n' + indent(errorMessge),
+      message: values.message + '\n' + indent(errorMessage),
     }
packages/log/src/formatters.ts-16-16 (1)

16-16: ⚠️ Potential issue | 🟡 Minor

Typo in function name: gerErrorFromLogRecordgetErrorFromLogRecord.

This appears to be an accidental rename introduced during the revert. The t is missing from get.

Proposed fix
-function gerErrorFromLogRecord(record: LogRecord): Error | null {
+function getErrorFromLogRecord(record: LogRecord): Error | null {

Also update the two call sites (lines 39 and 47):

-  const error = gerErrorFromLogRecord(record)
+  const error = getErrorFromLogRecord(record)
packages/vscode/src/node/useMcpRegistration.ts-28-28 (1)

28-28: ⚠️ Potential issue | 🟡 Minor

Non-null assertion removes the previous safety guard.

The old code checked extensionContext.value and threw a descriptive error if unavailable. The ! assertion will produce an opaque TypeError if extensionContext is ever null here. Since this is a revert PR, this is acceptable if it restores the prior state, but worth noting the loss of a clear error message.

packages/vscode/src/useWorkspaceId.ts-7-21 (1)

7-21: ⚠️ Potential issue | 🟡 Minor

JSDoc is misleading — workspace state persistence is commented out.

Line 9 claims the ID is "generated once per workspace and stored in the workspace state," but the active code on Line 21 generates a fresh ID every session (the singleton ensures once-per-session, not once-per-workspace). The commented-out code (Lines 12–20) would have implemented persistence, but it's inactive.

Either update the JSDoc to reflect reality ("generated once per extension session") or uncomment the persistence logic. Also, extensionContext (Line 4) is unused in active code since the workspace-state path is commented out.

Option A: Fix the JSDoc to match active behavior
 /**
  * Get or create a unique workspace ID. This is used to correlate the MCP server with the workspace.
- * It is generated once per workspace and stored in the workspace state.
+ * It is generated once per extension session (not persisted across sessions).
  */
packages/likec4/src/cli/export/json/handler.ts-94-98 (1)

94-98: ⚠️ Potential issue | 🟡 Minor

Edge case: all projects empty yields an empty array or undefined in the output.

If every project is skipped (empty), projectsModels has length 0 and the ternary falls through to the array branch, writing [] to the JSON file. This is silently successful, which may surprise users. Consider warning or erroring when there's nothing to export.

packages/likec4/src/cli/import/drawio/handler.ts-45-53 (1)

45-53: ⚠️ Potential issue | 🟡 Minor

Output directory is not created before writing.

If the user specifies --outfile in a directory that doesn't exist, writeFile will throw ENOENT. The sibling export handler (export/drawio/handler.ts, Line 59) calls mkdir(outdir, { recursive: true }) before writing. Consider doing the same here.

Proposed fix
+import { mkdir, readFile, writeFile } from 'node:fs/promises'
-import { readFile, writeFile } from 'node:fs/promises'
+      await mkdir(dirname(outfile), { recursive: true })
       await writeFile(outfile, likec4Source)
packages/likec4/src/cli/index.ts-23-23 (1)

23-23: ⚠️ Potential issue | 🟡 Minor

Typo: configurelogger should be configureLogger (camelCase).

The local wrapper function is named configurelogger (all lowercase) while the imported function it wraps is configureLogger. This breaks camelCase convention and is inconsistent with the import on Line 3.

Proposed fix
-function configurelogger(isDebug = isDevelopment) {
+function configureLogger_(isDebug = isDevelopment) {

Or simply rename to configureLogger if there's no shadowing concern (there is — the import has the same name). An alternative is to alias the import:

-import { configureLogger, getAnsiColorFormatter, getConsoleFormatter, getConsoleSink } from '@likec4/log'
+import { configureLogger as setupLogger, getAnsiColorFormatter, getConsoleFormatter, getConsoleSink } from '@likec4/log'

and use setupLogger inside the local function body.

apps/playground/src/components/drawio/useDrawioActions.ts-23-39 (1)

23-39: ⚠️ Potential issue | 🟡 Minor

Missing reader.onerror handler — file-read failures are silently ignored.

If FileReader.readAsText encounters an error (e.g., permission denied, file locked), no feedback is provided to the user.

Proposed fix
       const reader = new FileReader()
+      reader.onerror = () => {
+        console.error('Failed to read DrawIO file', reader.error)
+      }
       reader.onload = () => {
packages/likec4/src/cli/export/drawio/handler.ts-50-57 (1)

50-57: ⚠️ Potential issue | 🟡 Minor

Remove unsafe as ProjectId cast — validate string input properly before passing to ensureProjectId.

Line 51 casts onlyProject (a yargs string option) directly to ProjectId without validation. While ensureProjectId does validate via invariant check, relying on runtime exceptions for type safety is fragile. The cast should be replaced with proper validation, similar to the ensureProjectId helper in packages/likec4/src/cli/utils.ts that validates string paths and returns a valid ProjectId.

packages/generators/src/drawio/parse-drawio.ts-95-96 (1)

95-96: ⚠️ Potential issue | 🟡 Minor

Dead branch: styleMap.get('fillColor') can never match.

parseStyle lowercases all keys on Line 49 (k.toLowerCase()), so the map will only ever contain 'fillcolor'. The fallback ?? styleMap.get('fillColor') is dead code. Same applies to strokeColor on Line 96.

Proposed fix
-    const fillColor = styleMap.get('fillcolor') ?? styleMap.get('fillColor')
-    const strokeColor = styleMap.get('strokecolor') ?? styleMap.get('strokeColor')
+    const fillColor = styleMap.get('fillcolor')
+    const strokeColor = styleMap.get('strokecolor')
packages/generators/src/drawio/generate-drawio.ts-199-205 (1)

199-205: ⚠️ Potential issue | 🟡 Minor

Remove <mxGeometry> from the default parent cell (id="1").

The default layer cell in Draw.io should not include geometry, as it is a container, not a drawable shape. Per the mxGraph/Draw.io standard structure, id="1" is the default layer and typically has no <mxGeometry>. Including geometry may cause Draw.io to misinterpret the layer or offset child positions unexpectedly.

🧹 Nitpick comments (21)
e2e/src/likec4-cli-build.spec.ts (1)

10-10: Nit: extra space in comment.

There's a double space before the URL.

Proposed fix
-// see  https://github.com/likec4/likec4/issues/2076
+// see https://github.com/likec4/likec4/issues/2076
packages/vscode/tsdown.config.ts (1)

112-113: process.exit(1) bypasses async cleanup; consider throwing instead.

Using process.exit(1) terminates immediately without letting tsdown's hook runner handle the failure or run any cleanup. Throwing an error would propagate naturally and still fail the build with a clear message. That said, since this is a revert PR restoring prior behavior, this is a minor nit.

♻️ Optional: throw instead of hard exit
-    console.error(`"${vscodePreview}" not found`)
-    process.exit(1)
+    throw new Error(`"${vscodePreview}" not found`)
packages/language-server/src/browser/index.ts (1)

21-22: Placeholder comment adds no value.

/* browser specific setup code */ is a vague comment with no actionable information. If this was meant as a marker for future work, a TODO with context would be clearer; otherwise, consider removing it.

Proposed cleanup
-  /* browser specific setup code */
-
   const messageReader = new BrowserMessageReader(port)
packages/language-server/src/browser/worker.ts (1)

6-9: No error handling around worker startup.

startLanguageServer(self) is invoked unconditionally with no try/catch or global error listeners. If startup fails, the worker will silently die in many browser environments, making debugging difficult. Since this is a revert to a prior known state, this is non-blocking, but worth considering for a follow-up.

Minimal error handling suggestion
-startLanguageServer(self)
+try {
+  startLanguageServer(self)
+} catch (e) {
+  console.error('Failed to start language server:', e)
+}
packages/log/src/index.ts (1)

61-61: Avoid <any, any> — use the function's own generic parameters instead.

The function already declares TSinkId and TFilterId; threading them through avoids the any escape hatch:

Proposed fix
-    configureLogtape<any, any>({
+    configureLogtape<TSinkId | 'console', TFilterId>({

As per coding guidelines, "TypeScript must use explicit types and avoid using any".

packages/vscode/src/node/mcp-server.ts (1)

39-43: Silent catch drops non-JSON LIKEC4_WORKSPACE values.

Since this is a revert, this restores the prior behavior. However, note that if LIKEC4_WORKSPACE is set to a plain path string (e.g. /home/user/project) rather than a JSON-encoded value (e.g. "\"/home/user/project\""), JSON.parse will throw and the value will be silently ignored — falling through to process.cwd() on line 49. The reverted PR (#2622) had added a fallback for this case.

If supporting plain path strings is desired, consider re-adding the fallback in a follow-up.

packages/log/src/formatters.ts (2)

87-87: Remove commented-out dead code.

This leftover comment serves no purpose and adds noise.


27-28: Consider using safeStringify() for non-Error values in error properties to preserve information.

In lines 27-28, when error/err properties contain non-Error values, wrapping with new Error(\${err}`)relies on template literal coercion via.toString(). For complex objects, this produces "[object Object]"and fornull/undefinedit produces"null"/"undefined", losing valuable debugging information. Using safeStringify(err, { indentation: '\t' })` (already available from the utils module) would better preserve the structure of non-Error values.

packages/likec4/src/cli/codegen/webcomponent/handler.ts (1)

76-97: Mixed logging surfaces: consola for debug/warn, logger for info.

The function now uses two different loggers — consola for debug-level and warning messages, and logger (from createLikeC4Logger) for info-level output. This split makes it harder to configure log levels consistently or filter output by source. Consider consolidating on one logger for the entire handler.

apps/playground/src/monaco/index.tsx (1)

15-15: Remove the commented-out export.

This dead comment adds no value and may confuse future readers.

🧹 Proposed fix
-// export { default as MonacoEditor } from './MonacoEditor'
packages/likec4/src/cli/export/png/handler.ts (2)

144-144: Double languageServices.languageServices is confusing.

The variable languageServices returned by fromWorkspace has a .languageServices property, making this read awkwardly. Consider aliasing to improve clarity (e.g., const lsp = languageServices.languageServices).


274-278: Redundant boolean: true alongside type: 'boolean' in yargs options.

Both seq and flat specify boolean: true and type: 'boolean'. One or the other suffices.

🧹 Proposed fix
           'seq': {
-            boolean: true,
             alias: ['sequence'],
             type: 'boolean',
             desc: 'use sequence layout for dynamic views',
           },
           'flat': {
             alias: ['flatten'],
-            boolean: true,
             type: 'boolean',
             desc: 'flatten all images in output directory ignoring sources structure',
           },
packages/generators/src/drawio/parse-drawio.spec.ts (1)

49-54: Edge case: the "empty" XML is structurally invalid Draw.io (no <diagram> wrapper).

The test input <mxfile><root>...</root></mxfile> omits the <diagram><mxGraphModel> wrapper that Draw.io produces. This is a fine robustness test, but consider adding a comment clarifying that it's intentionally malformed to test graceful degradation.

packages/generators/src/drawio/generate-drawio.spec.ts (1)

48-52: Mock uses unsafe double cast but works due to defensive code in generateDrawio.

The mock only provides $view, but generateDrawio also accesses $styles (lines 66, 82) and bounds (line 220). While this works at runtime because both properties are checked defensively (safe guard with '$styles' in viewmodel and try-catch for bounds), the type safety is still compromised. Adding at least the $styles property to the mock would improve type safety for future changes.

apps/playground/src/routes/w.$workspaceId/route.tsx (1)

2-3: Merge duplicate imports from the same module.

Lines 2 and 3 both import from '$components/drawio/DrawioContextMenuProvider'. Combine into a single import statement.

-import { DrawioContextMenuProvider } from '$components/drawio/DrawioContextMenuProvider'
-import { useDrawioContextMenu } from '$components/drawio/DrawioContextMenuProvider'
+import { DrawioContextMenuProvider, useDrawioContextMenu } from '$components/drawio/DrawioContextMenuProvider'
packages/generators/src/drawio/generate-drawio.ts (3)

134-142: Production code accommodates test-only position/size fields.

The getBBox helper casts to a union with position?: [number, number] and size?: { width: number; height: number } solely to support "legacy mocks." Production code shouldn't carry compatibility shims for test fixtures. Instead, fix the test mocks to use the real DiagramNode shape (x, y, width, height).


66-76: Fragile internal property access and bare as string casts.

'$styles' in viewmodel probes an internal/undocumented property, and values.elements.fill as string assumes a specific return shape without validation. If $styles or .colors() return type changes, this will silently produce undefined fill/stroke values baked into the XML.

Consider narrowing the type or adding a runtime check (e.g. typeof values.elements.fill === 'string').


124-132: Non-transitive sort comparator may produce unpredictable ordering.

The comparator returns 0 for many non-equal pairs (e.g. two nodes with different non-null parents that aren't ancestors of each other). Non-transitive comparators lead to implementation-dependent sort results. Consider a topological sort or sorting by depth first, then by parent, which guarantees parents are emitted before children.

packages/generators/src/drawio/parse-drawio.ts (3)

28-32: Low risk: regex built from name parameter — safe in current usage but fragile.

The static analysis tool flagged this as a potential ReDoS vector. All current callers pass hardcoded attribute names ('id', 'value', 'x', etc.), so there's no exploitable risk today. However, if a future caller passes untrusted input, the unescaped name could break the regex or enable ReDoS.

Consider escaping name or switching to a simple indexOf/slice approach:

Alternative: indexOf-based extraction
 function getAttr(attrs: string, name: string): string | undefined {
-  const re = new RegExp(`${name}="([^"]*)"`, 'i')
-  const m = attrs.match(re)
-  return m ? m[1] : undefined
+  const search = `${name}="`
+  const idx = attrs.toLowerCase().indexOf(search.toLowerCase())
+  if (idx < 0) return undefined
+  const start = idx + search.length
+  const end = attrs.indexOf('"', start)
+  return end > start ? attrs.slice(start, end) : undefined
 }

293-300: Every element without children/desc/tech/color gets an empty block.

When hasBody is false, the code emits an empty { } block (Lines 296–297). This produces noisy output like:

  myElement = container 'My Element'
  {
  }

Consider omitting the block entirely for leaf elements with no metadata.

Proposed fix
     } else {
-      lines.push(`${pad}{`)
-      lines.push(`${pad}}`)
+      // No body needed — omit empty block
     }

72-72: Unnecessary indirection: geomAttr is just an alias for getAttr.

const geomAttr = (tag: string, name: string) => getAttr(tag, name) adds a wrapper that does nothing. Use getAttr directly on Lines 91–94.

Comment on lines +1 to +75
# Beginner Nontes

## Introduction

These are the notes for beginners to the project, or Javascript/Typescript eco-system.
(They can be ignored for the experienced and knowledgeable.)

## For the required version of nodejs and pnpm, dprint

- Install asdf and use it, which will follow the ./.tool-versions satisfying the version requirements
- Note, the already installed version of nodejs (node) in Ubuntu might not be the expected
version, as there is no new install, asdf might not have the chance to enforce
the nodejs' version.
If that is the case, you should manually install the correct version and use it.

## pnpm build failure of "FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory"

Try
```
NODE_OPTIONS="--max-old-space-size=4096" pnpm build
```

I had the error in Ubuntu/WSL 2, the solution worked.

To make it the setting permanent, add the following to ~/.bashrc

```
shell
# Avodi pnmp build failure:
export NODE_OPTIONS="--max-old-space-size=4096"
# end of Avodi pnmp build failure
```

## dprint installation

Install locally,

```
npm install dprint
```

this would have the error in VS Code and its derivations that dprint is not accessible.

The local dprint has to be accessed via (which VS Code, etc. would not assumed so):

```
npx dprint
```

To install globally would not have the issue:

```
npm install -g dprint
```

It seems that the local installation does not have any problem with the
project's setup, as I have not seen any error when running the tasks in the project.

## To propagate any update in the project to the VS Code extension and try with the modified extension in VS Code

Here are the steps:
1. Execute at the project root,
```
pnpm turbo run build --filter='@likec4/vscode-preview' --filter='likec4-vscode'`
```
2. In a VS Code session, in "Run and Debug view", select "Run Extension" and run it

Then in the newly spawn VS Code session,
the local extension with the modifications will be in effect.

## Running VS Code with the local modified extension in Ubluntu/WSL2 might block
the Ubluntu/WSL2's operation

I found a git commit Ubluntu/WSL2 was too slow to complete when
Running VS Code with the local modified extension in the Ubluntu/WSL2
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This file appears to be personal developer notes accidentally included in the PR.

This is a revert PR, but BEGINNER_NONTES.md is a new file that doesn't belong to the reverted feature. It also has numerous issues:

  1. Filename typo: BEGINNER_NONTES.mdBEGINNER_NOTES.md
  2. Title typo (Line 1): "Nontes" → "Notes"
  3. "Ubluntu" (Lines 71, 72, 74): should be "Ubuntu"
  4. "Avodi pnmp" (Lines 29, 31): should be "Avoid pnpm"
  5. Stray shell on Line 28 — likely meant as a code fence language specifier (\``shell`), not content inside the block
  6. Trailing backtick on Line 64 in the command string
  7. All code blocks are missing language specifiers (flagged by markdownlint MD040)

Consider removing this file from the PR entirely, or if it's intended as project documentation, it needs significant cleanup and likely belongs in a CONTRIBUTING.md or wiki instead.

🧰 Tools
🪛 LanguageTool

[grammar] ~1-~1: Ensure spelling is correct
Context: # Beginner Nontes ## Introduction These are the notes for be...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[style] ~42-~42: Consider using “inaccessible” to avoid wordiness.
Context: ...Code and its derivations that dprint is not accessible. The local dprint has to be accessed v...

(NOT_ABLE_PREMIUM)


[style] ~55-~55: Consider a more formal alternative to strengthen your wording.
Context: ... issue: npm install -g dprint It seems that the local installation does not ha...

(SEEM_APPEAR)


[grammar] ~71-~71: Ensure spelling is correct
Context: ...de with the local modified extension in Ubluntu/WSL2 might block the Ubluntu/WSL2's ope...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)


[grammar] ~72-~72: Ensure spelling is correct
Context: ...tension in Ubluntu/WSL2 might block the Ubluntu/WSL2's operation I found a git commit...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.20.0)

[warning] 19-19: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 27-27: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 38-38: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 46-46: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 52-52: Fenced code blocks should have a language specified

(MD040, fenced-code-language)


[warning] 63-63: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In `@BEGINNER_NONTES.md` around lines 1 - 75, The file BEGINNER_NONTES.md appears
to be accidental developer notes — either remove it from the PR or clean and
relocate it; if keeping it, rename the file to BEGINNER_NOTES.md, fix title
"Beginner Nontes" → "Beginner Notes", correct typos "Ubluntu" → "Ubuntu" and
"Avodi pnmp" → "Avoid pnpm", remove the stray literal "shell" inside the code
block and change the fence to ```shell, remove the trailing backtick in the pnpm
turbo command, and add language specifiers to all fenced code blocks (e.g.,
```bash or ```shell); consider moving the cleaned content into CONTRIBUTING.md
or project wiki instead of this PR.

Comment on lines +140 to +161
{!hasNotations && (
<m.div
key={'empty'}
initial={{ opacity: 0.75, translateX: '50%' }}
animate={{ opacity: 1, translateX: 0 }}
exit={{
translateX: '100%',
opacity: 0.6,
}}
className={styles.container}>
<Tooltip label="View has no notations" color="orange" {...portalProps}>
<ThemeIcon
size={'lg'}
variant="light"
color="orange"
radius={'md'}
>
<IconAlertTriangle />
</ThemeIcon>
</Tooltip>
</m.div>
)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get the file history with more detail
git log --oneline --all -- packages/diagram/src/likec4diagram/ui/notation/NotationPanel.tsx | head -20

Repository: likec4/likec4

Length of output: 229


🏁 Script executed:

#!/bin/bash
# Find the commit that introduced `#2622`
git log --all --grep="#2622" --oneline | head -5

Repository: likec4/likec4

Length of output: 142


🏁 Script executed:

#!/bin/bash
# Get current file to see if it contains the empty-state
if [ -f packages/diagram/src/likec4diagram/ui/notation/NotationPanel.tsx ]; then
  echo "=== Current file lines 140-165 ==="
  sed -n '140,165p' packages/diagram/src/likec4diagram/ui/notation/NotationPanel.tsx
else
  echo "File not found"
fi

Repository: likec4/likec4

Length of output: 872


🏁 Script executed:

#!/bin/bash
# Get the parent commit of `#2622` to see the original state before the feature was added
git show fdd46ff9^:packages/diagram/src/likec4diagram/ui/notation/NotationPanel.tsx | sed -n '130,170p'

Repository: likec4/likec4

Length of output: 96


🏁 Script executed:

#!/bin/bash
# Get the file state right after `#2622` was applied
git show fdd46ff9:packages/diagram/src/likec4diagram/ui/notation/NotationPanel.tsx | sed -n '130,170p'

Repository: likec4/likec4

Length of output: 1216


🏁 Script executed:

#!/bin/bash
# Get what the revert commit changed - show the diff
git show c6a872f2 -- packages/diagram/src/likec4diagram/ui/notation/NotationPanel.tsx | head -100

Repository: likec4/likec4

Length of output: 2946


Empty-state UI is a net-new feature inconsistent with the revert scope.

Before #2622, this component had an early return (if (!hasNotations) return null) that rendered nothing when notations were absent. The revert should restore this behavior, but instead introduces a brand-new empty-state block with an orange alert triangle and tooltip. This is not a restoration of original code—it's a new feature being added under the guise of a revert.

Additionally, displaying a warning-style icon for views with no notations may mislead users into thinking an error occurred, when many views legitimately have no notations.

If this empty-state UI is intentional, split it into a separate PR for clarity; do not bundle it with the revert.

🤖 Prompt for AI Agents
In `@packages/diagram/src/likec4diagram/ui/notation/NotationPanel.tsx` around
lines 140 - 161, Restore the original revert behavior by removing the new
empty-state UI block: delete the conditional JSX that renders the <m.div
key={'empty'} ...> containing Tooltip, ThemeIcon and IconAlertTriangle when
!hasNotations, and instead reinstate the early-return pattern (if
(!hasNotations) return null) in NotationPanel (where hasNotations is computed)
so the component renders nothing when there are no notations; if an empty-state
is desired, move that JSX into a separate PR/component rather than keeping it in
this revert.

Comment on lines +160 to +162
const userData = desc !== '' || tech !== ''
? `<mxUserObject><data key="likec4Description">${desc}</data><data key="likec4Technology">${tech}</data></mxUserObject>\n `
: ''
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

mxUserObject placed incorrectly inside mxCell.

In Draw.io's schema, mxUserObject is a replacement for mxCell (it wraps the cell), not a child of it. Embedding <mxUserObject> inside <mxCell> content means Draw.io will ignore the custom data when re-importing. The description and technology metadata will be silently lost on round-trip.

The correct approach is to emit <UserObject … > (or <object>) as the outer tag when custom data is present, with the <mxCell> nested inside it.

🤖 Prompt for AI Agents
In `@packages/generators/src/drawio/generate-drawio.ts` around lines 160 - 162,
The current code builds userData as an <mxUserObject> string and then injects it
as a child inside the existing <mxCell>, which is incorrect; instead, when
description/technology exist (userData non-empty) wrap the entire cell with an
outer <mxUserObject> (or <UserObject>), put the <mxCell> as its child, and place
the description/technology <data> entries inside that outer element; locate the
assembly logic that builds userData and the place where the <mxCell> is emitted
(symbols: userData, mxUserObject, mxCell in generate-drawio.ts) and change the
output composition so that if userData is non-empty you emit
<mxUserObject>…<mxCell>…</mxCell></mxUserObject> rather than embedding
<mxUserObject> inside the <mxCell>.

return `<?xml version="1.0" encoding="UTF-8"?>
<mxfile host="LikeC4" modified="${new Date().toISOString()}" agent="LikeC4" version="1.0" etag="" type="device">
<diagram name="${escapeXml(view.id)}" id="likec4-${escapeXml(view.id)}">
<mxGraphModel dx="800" dy="800" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale=1 pageWidth="827" pageHeight="1169" math="0" shadow="0">
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Invalid XML: unquoted attribute value pageScale=1.

XML requires all attribute values to be quoted. pageScale=1 will produce malformed XML. Should be pageScale="1".

Proposed fix
-      <mxGraphModel dx="800" dy="800" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale=1 pageWidth="827" pageHeight="1169" math="0" shadow="0">
+      <mxGraphModel dx="800" dy="800" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<mxGraphModel dx="800" dy="800" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale=1 pageWidth="827" pageHeight="1169" math="0" shadow="0">
<mxGraphModel dx="800" dy="800" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="827" pageHeight="1169" math="0" shadow="0">
🤖 Prompt for AI Agents
In `@packages/generators/src/drawio/generate-drawio.ts` at line 210, The
MXGraphModel element in the drawio XML output contains an unquoted attribute
pageScale=1 which produces invalid XML; update the string/template that emits
the MXGraphModel (look for the literal '<mxGraphModel dx="800" ... pageScale=1
pageWidth="827" ...' in generate-drawio.ts) to quote the value as pageScale="1"
so all attributes are properly quoted and the generated XML is well-formed.

Comment on lines +56 to 63
function parseUserData(fullTag: string): { description?: string; technology?: string } {
const out: { description?: string; technology?: string } = {}
const descMatch = fullTag.match(/<data\s+key="likec4Description"[^>]*>([\s\S]*?)<\/data>/i)
if (descMatch?.[1]) out.description = decodeXmlEntities(descMatch[1].trim())
const techMatch = fullTag.match(/<data\s+key="likec4Technology"[^>]*>([\s\S]*?)<\/data>/i)
if (techMatch?.[1]) out.technology = decodeXmlEntities(techMatch[1].trim())
return out
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Round-trip with standard Draw.io files won't preserve user data.

parseUserData looks for <data key="likec4Description"> inside the <mxCell> inner content (Line 90 passes inner). However, in standard Draw.io, custom data lives in <UserObject> or <object> elements that wrap <mxCell>, not inside it. Combined with the generation-side issue in generate-drawio.ts (where mxUserObject is incorrectly nested inside mxCell), the self-round-trip might coincidentally work, but importing files created by the real Draw.io editor will silently lose description/technology metadata.

Also applies to: 86-90

🤖 Prompt for AI Agents
In `@packages/generators/src/drawio/parse-drawio.ts` around lines 56 - 63,
parseUserData currently only searches for <data key="likec4Description"> inside
the mxCell inner content so it misses standard Draw.io custom data which is
stored in a wrapping <UserObject> or <object>; update parseUserData to also
detect and parse <UserObject> or <object> elements that wrap an <mxCell>
(extract their <data key="likec4Description"> and <data
key="likec4Technology">), and update the generator in generate-drawio (where
mxUserObject is currently nested inside mxCell) to emit the
mxUserObject/UserObject/object as a sibling/wrapper of <mxCell> consistent with
Draw.io export so round-trips preserve description and technology.

Comment on lines +43 to +46
const likec4 = await fromWorkspace(args.path, {
graphviz: useDotBin ? 'binary' : 'wasm',
watch: false,
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

fd -t f 'options.ts' --full-path 'packages/likec4/src/cli' -exec head -100 {}

Repository: likec4/likec4

Length of output: 156


🏁 Script executed:

cat -n packages/likec4/src/cli/export/drawio/handler.ts | head -60

Repository: likec4/likec4

Length of output: 2560


🏁 Script executed:

rg -n "useDotBin|use-dot|use.dot" --type=ts packages/likec4/src/cli -B2 -A2

Repository: likec4/likec4

Length of output: 24999


Bug: useDotBin is the yargs option-definition object, not the parsed boolean — graphviz will always be 'binary'.

useDotBin is imported from ../../options (Line 10) and is an option configuration object (e.g., { type: 'boolean', ... }). Since any non-null object is truthy, the ternary useDotBin ? 'binary' : 'wasm' on line 44 will always resolve to 'binary', ignoring the user's actual --use-dot flag.

Use args['use-dot'] instead to access the parsed boolean value.

Proposed fix
       const likec4 = await fromWorkspace(args.path, {
-        graphviz: useDotBin ? 'binary' : 'wasm',
+        graphviz: args['use-dot'] ? 'binary' : 'wasm',
         watch: false,
       })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const likec4 = await fromWorkspace(args.path, {
graphviz: useDotBin ? 'binary' : 'wasm',
watch: false,
})
const likec4 = await fromWorkspace(args.path, {
graphviz: args['use-dot'] ? 'binary' : 'wasm',
watch: false,
})
🤖 Prompt for AI Agents
In `@packages/likec4/src/cli/export/drawio/handler.ts` around lines 43 - 46, The
current call to fromWorkspace uses the option-definition object useDotBin in the
ternary (useDotBin ? 'binary' : 'wasm'), which is always truthy; update the
graphviz selection to use the parsed CLI value args['use-dot'] instead (e.g.,
graphviz: args['use-dot'] ? 'binary' : 'wasm') in the fromWorkspace invocation
inside the handler (the block that constructs likec4), leaving the imported
useDotBin option definition unchanged.

Comment on lines +52 to +55
const languageServices = await fromWorkspace(args.path, {
graphviz: useDotBin ? 'binary' : 'wasm',
watch: false,
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's find the options file and see what useDotBin is
fd 'options' -t f --extension ts -g '*/cli/*'

Repository: likec4/likec4

Length of output: 151


🏁 Script executed:

# Now let's read the handler.ts file to see the full context
cat -n packages/likec4/src/cli/export/json/handler.ts | head -80

Repository: likec4/likec4

Length of output: 3644


🏁 Script executed:

# Let's also check the options file to see the useDotBin definition
cat -n packages/likec4/src/cli/options.ts

Repository: likec4/likec4

Length of output: 3279


🏁 Script executed:

# Let's check the png handler to compare the pattern
fd 'png.*handler' -t f --extension ts

Repository: likec4/likec4

Length of output: 39


🏁 Script executed:

# Let's search for other usages of useDotBin in handler contexts
rg -n 'graphviz.*useDotBin' --type=ts -B5 -A5

Repository: likec4/likec4

Length of output: 11767


🏁 Script executed:

# Let's check if HandlerParams is defined and what it contains
rg -n 'type HandlerParams|interface HandlerParams' --type=ts -A10

Repository: likec4/likec4

Length of output: 6143


🏁 Script executed:

# Let's specifically check the json handler to see if it defines HandlerParams
cat -n packages/likec4/src/cli/export/json/handler.ts | tail -50

Repository: likec4/likec4

Length of output: 2042


🏁 Script executed:

# Let's check the drawio handler to see the pattern
cat -n packages/likec4/src/cli/export/drawio/handler.ts | head -50

Repository: likec4/likec4

Length of output: 2119


Bug: useDotBin refers to the yargs option definition, not the CLI argument value.

useDotBin imported from ../../options (line 10) is an option definition object, which is always truthy. This causes graphviz to always resolve to 'binary' regardless of the --use-dot flag passed by the user.

Should use args['use-dot'] instead.

🐛 Proposed fix
       const timer = startTimer(logger)
       const languageServices = await fromWorkspace(args.path, {
-        graphviz: useDotBin ? 'binary' : 'wasm',
+        graphviz: args['use-dot'] ? 'binary' : 'wasm',
         watch: false,
       })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const languageServices = await fromWorkspace(args.path, {
graphviz: useDotBin ? 'binary' : 'wasm',
watch: false,
})
const languageServices = await fromWorkspace(args.path, {
graphviz: args['use-dot'] ? 'binary' : 'wasm',
watch: false,
})
🤖 Prompt for AI Agents
In `@packages/likec4/src/cli/export/json/handler.ts` around lines 52 - 55, The
graphviz selection is using the imported option definition useDotBin (always
truthy) instead of the CLI value, so update the call that creates
languageServices (the fromWorkspace invocation that assigns languageServices) to
read the actual flag from args (use args['use-dot']) and set graphviz:
args['use-dot'] ? 'binary' : 'wasm'; keep the rest of the options (watch: false)
unchanged and ensure you reference the same symbols (fromWorkspace,
languageServices, graphviz) so the change is localized.

Comment on lines +152 to +165
if (!serverUrl) {
logger.info(k.cyan(`start preview server`))
server = await viteDev({
languageServices,
buildWebcomponent: false,
openBrowser: false,
hmr: false,
})
serverUrl = resolveServerUrl(server)
if (!serverUrl) {
logger.error('Vite server is not ready, no resolvedUrls')
throw new Error('Vite server is not ready, no resolvedUrls')
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Server resource leak: server.close() is skipped when the export loop throws.

If pngHandler throws on line 228 (export failures), the Vite dev server started at line 154 is never closed because lines 232–237 are unreachable. Wrap the export loop and cleanup in a try/finally.

🐛 Proposed fix — wrap in try/finally
+  try {
     for (const prj of projects) {
       // ... export logic ...
     }
+  } finally {
-  if (server) {
-    logger.info(k.cyan(`stop server`))
-    await server.close().catch(e => {
-      logger.error(e)
-    })
-  }
+    if (server) {
+      logger.info(k.cyan(`stop server`))
+      await server.close().catch(e => {
+        logger.error(e)
+      })
+    }
+  }

Also applies to: 232-237

🤖 Prompt for AI Agents
In `@packages/likec4/src/cli/export/png/handler.ts` around lines 152 - 165, The
Vite dev server started when serverUrl is falsy (via viteDev in the server
variable) may never be closed if the export loop (pngHandler) throws; wrap the
export loop and subsequent cleanup in a try/finally so server.close() always
runs. Concretely, around the code that calls pngHandler (and any export loop
that can throw), introduce try { /* export logic */ } finally { if (server)
await server.close(); } — reference the server variable, viteDev(),
resolveServerUrl(), and the pngHandler call so you close the started server
regardless of exceptions.

Comment on lines +94 to +96
main().catch(() => {
exit(1)
}

main().catch(exitWithFailure)
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Errors from main() are silently swallowed.

The .catch(() => { exit(1) }) discards the error entirely. If the CLI fails (e.g., invalid workspace path, missing files), the user sees no output — just a non-zero exit code. Consider logging the error before exiting:

-main().catch(() => {
+main().catch((err) => {
+  console.error(err)
   exit(1)
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
main().catch(() => {
exit(1)
}
main().catch(exitWithFailure)
})
main().catch((err) => {
console.error(err)
exit(1)
})
🤖 Prompt for AI Agents
In `@packages/likec4/src/cli/index.ts` around lines 94 - 96, The current catch on
main() swallows the thrown error; update the catch handler for main() to accept
the error (e.g., err) and log it before calling exit(1). Locate the invocation
main().catch(() => { exit(1) }) and change it to a catch that prints the error
(using console.error or process.stderr) with a brief context message and then
calls exit(1), so failures like invalid workspace paths are visible to the user.

Comment on lines +49 to 51
hasErrorLogged: function(_error: any): boolean {
throw new Error('Function not implemented.')
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

hasErrorLogged throws at runtime — return false instead.

Vite's logger interface may call hasErrorLogged during error deduplication. Throwing here will crash the process with an unhelpful message. A no-op stub returning false is safer and still satisfies the interface contract.

🐛 Proposed fix
-    hasErrorLogged: function(_error: any): boolean {
-      throw new Error('Function not implemented.')
+    hasErrorLogged: function(_error: any): boolean {
+      return false
     },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
hasErrorLogged: function(_error: any): boolean {
throw new Error('Function not implemented.')
},
hasErrorLogged: function(_error: any): boolean {
return false
},
🤖 Prompt for AI Agents
In `@packages/likec4/src/logger.ts` around lines 49 - 51, The hasErrorLogged
function currently throws an Error at runtime; change its implementation to be a
no-op that returns false instead. Locate the hasErrorLogged method in logger.ts
and replace the throw new Error('Function not implemented.') with a simple
return false so the function satisfies Vite's logger interface without crashing
during error deduplication.

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.

1 participant