Conversation
… Draw.io" (likec4#2629) This reverts commit 654240a, reversing changes made to fdd46ff.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
📝 WalkthroughWalkthroughThis PR introduces comprehensive Draw.io round-trip support with layout/color/waypoint preservation, adds cross-platform postpack infrastructure, removes the CLI import command, refactors Playground DrawIO export flows with multi-view generation, expands e2e testing, updates GitHub Actions runners, and adds Docker-based CI validation scripts. Changes
Sequence DiagramsequenceDiagram
participant User as User
participant Playground as Playground UI
participant LSP as Language Server (Layout)
participant Generator as DrawIO Generator
participant Exporter as DrawIO Exporter
User->>Playground: Right-click canvas for context menu
activate Playground
Playground->>Playground: useDrawioContextMenuActions hook
alt Export Single View
Playground->>LSP: Request layout (optional)
activate LSP
LSP-->>Playground: layoutedModel / layout data
deactivate LSP
Playground->>Generator: toViewModel + buildOptions
activate Generator
Generator-->>Playground: DrawioViewModelLike
deactivate Generator
Playground->>Exporter: generateDrawio(viewModel, options)
activate Exporter
Exporter-->>Playground: XML string
deactivate Exporter
else Export All Views
Playground->>LSP: collectViewModelsForExportAll
activate LSP
LSP->>LSP: fillFromLayoutedModel phase
LSP->>LSP: fillFromViewStates phase
LSP->>LSP: fillFromLayoutViews phase
LSP->>LSP: fillFromModelView phase
LSP-->>Playground: DrawioViewModelLike[]
deactivate LSP
Playground->>Exporter: generateDrawioMulti(viewModels, optionsByViewId)
activate Exporter
Exporter-->>Playground: Multi-tab XML
deactivate Exporter
end
Playground->>Exporter: downloadDrawioBlob(xml, filename)
Exporter-->>User: Download .drawio file
deactivate Playground
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 14
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
packages/vscode/src/node/mcp-server.ts (1)
40-49:⚠️ Potential issue | 🟡 MinorInconsistent URI handling between JSON-parsed and fallback paths.
Line 43 uses
URI.parse(v)which expects URI-formatted strings (e.g.,file:///home/user), while line 49 usesURI.file(trimmed)which correctly handles plain filesystem paths. If users provide plain paths in JSON format (e.g.,["/home/user/workspace"]),URI.parsewill misinterpret them.Consider using consistent handling:
🔧 Proposed fix for consistent URI handling
const parsed = envSchema.parse(JSON.parse(value)) const asArray = isString(parsed) ? [parsed] : parsed if (hasAtLeast(asArray, 1)) { - return map(asArray, v => URI.parse(v).fsPath) + return map(asArray, v => { + // Handle both URI strings (file://...) and plain paths + return v.startsWith('file://') ? URI.parse(v).fsPath : URI.file(v).fsPath + }) }Alternatively, update the JSDoc to explicitly document that JSON values must be URI strings (e.g.,
file:///path/to/workspace).packages/likec4/src/cli/export/png/takeScreenshot.ts (1)
141-148:⚠️ Potential issue | 🔴 CriticalFix Playwright API usage:
locator.evaluate()options parameter position.The call passes the options object as the second parameter, but the correct Playwright signature for Node.js/TypeScript is
locator.evaluate(pageFunction, arg?, options?). The timeout option must be the third parameter:locator.evaluate<unknown, HTMLImageElement>( image => image.complete || new Promise(resolve => { image.onload = resolve image.onerror = resolve }), undefined, { timeout: Math.max(15_000, timeout) } )Currently, the timeout option may not be applied as intended.
🤖 Fix all issues with AI agents
In `@apps/playground/src/monaco/index.tsx`:
- Around line 11-16: Add a JSDoc block above the exported function MonacoEditor
describing the component purpose, its parameter (props: MonacoEditorProps) and
the return type (JSX.Element or React.ReactElement), documenting any important
props or behavior (e.g., uses Suspense and LazyMonacoEditor, fallback Loader).
Keep it concise and follow existing JSDoc style in the repo.
- Around line 1-5: The import ordering is wrong: move the type-only import for
MonacoEditorProps before the value imports (i.e., place "import type {
MonacoEditorProps } from './MonacoEditor'" above the imports for Loader, lazy,
Suspense) so type-only imports are grouped first; keep the "export type {
MonacoEditorProps } from './MonacoEditor'" line as-is (or directly after the
grouped type import) to satisfy the linter.
In `@apps/playground/src/monaco/LanguageClientSync.tsx`:
- Around line 43-51: Add a JSDoc comment for the exported function
LanguageClientSync describing its purpose and public API: document the function
behavior and each parameter (config: CustomWrapperConfig, wrapper:
MonacoEditorLanguageClientWrapper, setLayoutedModelApi?: (api: LayoutedModelApi
| null) => void), include the expected return type (React component) and any
side effects (e.g., setting layouted model API or initializing language client),
and tag it as exported/ public; keep the comment concise and follow existing
JSDoc style used in the codebase.
In `@apps/playground/src/monaco/MonacoEditor.tsx`:
- Around line 1-3: The import grouping is incorrect: move the type-only
import(s) before value imports and ensure they use the TypeScript `import type`
form; specifically, place `import type { LayoutedModelApi } from
'$components/drawio/DrawioContextMenuProvider'` above the value imports (`import
{ usePlayground } from '$/hooks/usePlayground'` and `import { loggable,
rootLogger } from '@likec4/log'`) so all type-only imports are grouped first and
then the runtime/value imports follow, then run the linter to verify ordering.
- Around line 18-20: Add a JSDoc block above the default-exported React
component LazyMonacoEditor describing its purpose, public props
(MonacoEditorProps), and behavior (e.g., it memoizes the editor, accepts
setLayoutedModelApi prop and uses usePlayground). Include param tags for the
props and a `@returns` tag describing the rendered React element, and mark it as
the public component for the Monaco editor.
In `@e2e/helpers/selectors.ts`:
- Around line 6-17: The exported helper functions canvas and editor lack
explicit return types; update the file to import the Locator type (e.g., add
"Locator" to the existing import type from '@playwright/test') and declare
explicit return types on the functions (canvas(page: Page): Locator and
editor(page: Page): Locator) so both helpers return a Locator type instead of
relying on inference.
In `@e2e/tests/drawio-playground.spec.ts`:
- Around line 1-4: The import ordering is incorrect: move the type-only import
"Page" (import type { Page } from '@playwright/test') above the value imports
and group all type-only imports first, then the runtime/value imports (expect,
test, readFile, canvas, CANVAS_SELECTOR, editor, MENU_SELECTOR); update the top
of the file so the "import type { Page }" line appears before the other imports
and keep the remaining imports (expect, test, readFile, and the selectors
canvas/CANVAS_SELECTOR/editor/MENU_SELECTOR) in their original order.
In `@e2e/tests/static-navigation.spec.ts`:
- Around line 1-4: The imports at the top are not grouped with type-only imports
first; move the type-only import "import type { Page } from '@playwright/test'"
before the value imports (e.g., "import { expect, test } from
'@playwright/test'", "import { canvas } from '../helpers/selectors'", "import {
TIMEOUT_CANVAS } from '../helpers/timeouts'") so that all type-only imports are
grouped and appear before runtime/value imports to satisfy the linter.
In `@packages/generators/src/drawio/parse-drawio.ts`:
- Around line 1054-1074: The code parses metadataJson into metaObj but the
type-check relies on "metaObj && typeof metaObj === 'object' &&
!Array.isArray(metaObj)"; change this to an explicit null/undefined guard (e.g.,
metaObj != null && typeof metaObj === 'object' && !Array.isArray(metaObj')) so
null is unambiguously excluded before iterating its entries; update the
conditional around the JSON.parse result in parse-drawio.ts (the block that
builds metaAttrs and pushes into bodyLines using escapeLikec4Quotes) to use the
explicit != null check.
In `@packages/icons/package.json`:
- Line 77: The change added/edited the "postpack" script entry in the icons
package's package.json (the "postpack" key); revert that edit so the generated
icons package remains untouched, or if this change was intentional, add an
explicit review approval note and update the generation script instead of
manually editing the package.json; locate the "postpack" key in the icons
package's package.json and either restore its original value from main branch or
document/approve the generator change that produces this file.
In `@packages/language-server/src/browser/worker.ts`:
- Around line 8-21: Refactor the if/else chain inside the errToString function
to use a switch(true) pattern while preserving all existing branch behavior and
order: test err instanceof Error, typeof === 'object' with null check and the
JSON.stringify try/catch fallback, typeof === 'string', null/undefined check,
typeof === 'number' or 'boolean', typeof === 'symbol' (call toString), and the
final default 'unknown' return; ensure no logic or return values change and keep
the function signature errToString(err: unknown): string unchanged.
In `@packages/language-server/src/documentation/documentation-provider.ts`:
- Around line 16-19: Add a JSDoc comment for the public override method
getDocumentation in the DocumentationProvider class: describe the method's
purpose, its parameters (types/meanings) and the return value, and include any
thrown errors or important behavior (e.g., async/Promise or cancellation
support) so it matches the existing JSDoc style used for the class and
constructor; target the getDocumentation method in DocumentationProvider and
ensure the comment is placed immediately above the method declaration and uses
the same JSDoc conventions as the file.
In `@packages/likec4/src/logger.ts`:
- Around line 17-20: Add a JSDoc comment block above the public debug method to
document its purpose and usage: describe that Logger.debug logs a debug-level
message, list parameters (msg: string, ...args: unknown[]) with types and note
that args is a variadic list of context values, and state the return type
(void). Reference the method name debug and the internal call to logger.debug in
the description so readers know this wraps the underlying logger; include a
short example or note about how args are attached (e.g., passed as an args
object) if helpful.
In `@packages/vscode/tsdown.config.ts`:
- Around line 109-113: The async function copyPreview lacks an explicit return
type; update its signature to declare a Promise<void> return type (i.e., change
function copyPreview() to function copyPreview(): Promise<void>) so TypeScript
explicit-types guideline is followed; keep the implementation unchanged and
ensure any callers still work with the Promise<void> signature.
🧹 Nitpick comments (19)
packages/vscode/src/utils.ts (1)
3-38: Add explicit return types to exported functions.Guidelines require explicit types; these exports currently rely on inference. Please annotate return types (and consider a named type for
performanceMarkfor clarity).✏️ Proposed fix
-export function now() { +export function now(): number { try { return performance.now() } catch { return Date.now() } } -export function performanceMark() { +export type PerformanceMark = { + readonly ms: number + readonly pretty: string +} + +export function performanceMark(): PerformanceMark { const t0 = now() return { get ms(): number { return now() - t0 }, get pretty(): string { return prettyMs(now() - t0) }, } } -export function isLikeC4Source(path: string) { +export function isLikeC4Source(path: string): boolean { const p = path.toLowerCase() return p.endsWith('.c4') || p.endsWith('.likec4') || p.endsWith('.like-c4') }.changeset/drawio-cli-roundtrip-e2e.md (1)
8-8: Minor clarity improvement: Add "files" after file extensions.For better readability, consider adding the word "files" after the file extensions.
📝 Suggested improvement
-- **CLI:** `likec4 export drawio --roundtrip` reads all `.c4`/`.likec4` in the workspace, parses round-trip comment blocks (layout, stroke colors/widths, edge waypoints), and applies them when generating each view's `.drawio` file. +- **CLI:** `likec4 export drawio --roundtrip` reads all `.c4`/`.likec4` files in the workspace, parses round-trip comment blocks (layout, stroke colors/widths, edge waypoints), and applies them when generating each view's `.drawio` file.apps/playground/scripts/generate.mts (1)
5-13: Consider storingplatform()result to avoid redundant calls.The Windows handling logic is correct. Minor nit:
platform()is called twice—once on line 6 and again on line 12. Whileplatform()is cheap (returns a cached value), storing the result improves readability.♻️ Optional: Store platform result in a variable
+const isWindows = platform() === 'win32' + // On Windows, zx needs PowerShell (no bash by default in v8+) -if (platform() === 'win32') { +if (isWindows) { usePowerShell() } const $ = _$({ stdio: 'inherit', preferLocal: true, - ...(platform() === 'win32' ? { quote: quotePowerShell } : {}), + ...(isWindows ? { quote: quotePowerShell } : {}), })packages/vscode/src/node/mcp-server.ts (1)
29-34: JSDoc should clarify expected input format.The JSDoc mentions "JSON array of paths" but doesn't specify whether these should be URI-formatted strings (
file:///...) or plain filesystem paths. Given the inconsistency noted above, clarifying the expected format would help users provide correct input.📝 Suggested JSDoc improvement
/** - * Read LIKEC4_WORKSPACE env: JSON array of paths or single path string. + * Read LIKEC4_WORKSPACE env: JSON array of file URIs (e.g., "file:///path") or single path string. * Falls back to treating raw value as one workspace path if not valid JSON. * `@param` name - Environment variable name (e.g. 'LIKEC4_WORKSPACE'). - * `@returns` Array of absolute fs paths, or undefined if unset/invalid. + * `@returns` Non-empty array of absolute fs paths, or undefined if unset/empty. */packages/log/src/index.ts (1)
65-70: Simplify redundantsinksextraction.Line 65 extracts
sinksas_sinks(unused), then line 66 re-extracts it viaconfig?.sinks. Use default value in destructuring instead.♻️ Suggested simplification
- const { sinks: _sinks, loggers: _loggers, ...restConfig } = config ?? {} - const sinks = config?.sinks ?? {} + const { sinks = {}, loggers: _loggers, ...restConfig } = config ?? {}packages/core/src/compute-view/deployment-view/stages/stage-exclude.spec.ts (1)
60-62: Consider tracking this limitation more formally.The added comments clearly document why this test is skipped and the underlying limitation. However, since the comment asks to "Decide: fix stage-exclude or adjust expectation," this appears to be an unresolved design question rather than a temporary skip.
Consider:
- Using
it.todoinstead ofit.skipto make this more visible in test reports as pending work- Creating a tracking issue to document the decision needed and ensure it's not forgotten
Alternative: Use it.todo for better visibility
- // Skip: excluding both ui and api leaves their parent deployment node (e.g. zone1) in final; implementation - // does not remove "empty" deployment nodes when all children are excluded. Decide: fix stage-exclude or adjust expectation. - it.skip('should remove element and all its connections', () => { + // Excluding both ui and api leaves their parent deployment node (e.g. zone1) in final; implementation + // does not remove "empty" deployment nodes when all children are excluded. Decide: fix stage-exclude or adjust expectation. + it.todo('should remove element and all its connections', () => {This makes the pending decision more visible in test output.
packages/config/src/filenames.ts (1)
47-62: Type predicate narrows to basename type, not the full path.The type predicates claim
filename is typeof configJsonFilenames[number], but the check is performed onbasename(filename). This means if you pass"/path/to/likec4.config.json", the function returnstrueand TypeScript narrows the type to'.likec4rc' | '.likec4.config.json' | 'likec4.config.json', even though the actual value is a full path.This is a common pattern and works for most use cases where you just need to know "is this a config file". However, if callers rely on the narrowed type expecting an exact filename, they may be surprised.
Consider whether a simple
booleanreturn is more accurate, or document this behavior:/** Returns true if the **basename** of the given path matches a known config filename. */ export function isLikeC4JsonConfig(filename: string): boolean { return (configJsonFilenames as readonly string[]).includes(basename(filename)) }packages/language-server/src/lsp/HoverProvider.ts (4)
83-85: Good addition of documentation.The JSDoc comment accurately describes the method's purpose. Consider enhancing it with
@paramand@returnstags for more comprehensive documentation:/** * Builds hover content for an element node (title, summary, model details and view links). * `@param` node - The element AST node to generate hover content for * `@returns` Hover information with markdown content, or undefined if element not found */This would provide more context for developers working with this class.
115-117: Documentation added effectively.The comment clearly explains the method's functionality. For completeness, consider adding parameter and return type documentation:
/** * Builds markdown hover content for an element model (relationship counts and links to views). * `@param` model - The element model containing relationship and view information * `@param` projectId - The project identifier for constructing view links * `@returns` Markdown string with model details, or undefined if no details available */
167-169: Documentation improves code clarity.The JSDoc comment appropriately describes the method. As with the other methods, consider using structured JSDoc tags:
/** * Builds hover content for a deployment node (id, title, kind, summary). * `@param` node - The deployment node AST node to generate hover content for * `@returns` Hover information with markdown content */
192-194: Documentation enhances maintainability.The comment accurately captures the method's behavior. For consistency with JSDoc best practices, consider the structured format:
/** * Builds hover content for a deployed instance (instance id, element ref, title and kind). * `@param` node - The deployed instance AST node to generate hover content for * `@returns` Hover information with markdown content showing instance details */packages/likec4/src/cli/export/json/handler.ts (1)
71-72: Extension check is case-sensitive.The check
extname(outfile) !== '.json'won't match uppercase variants like.JSON. This is a minor edge case but could cause files likeoutput.JSON.json.♻️ Proposed fix for case-insensitive check
- if (extname(outfile) !== '.json') outfile = outfile + '.json' + if (extname(outfile).toLowerCase() !== '.json') outfile = outfile + '.json'packages/likec4/src/cli/export/png/handler.ts (1)
346-354: Inconsistent property access for yargs args could cause undefined values.The handler mixes kebab-case and camelCase property access:
- Line 336:
args['max-attempts'](kebab-case)- Line 346:
args.maxAttempts(camelCase)- Line 349:
args.serverUrl(camelCase) for option'server-url'While yargs performs camelCase conversion by default, this inconsistency makes the code harder to maintain and could break if yargs configuration changes. Consider using consistent kebab-case access throughout to match the option definitions.
♻️ Proposed fix for consistent property access
await pngHandler( { path: args.path, useDotBin: args['use-dot'], output: args.outdir, project: args.project, timeoutMs: args.timeout * 1000, - maxAttempts: args.maxAttempts, + maxAttempts: args['max-attempts'], ignore: args.ignore === true, outputType: args.flat ? 'flat' : 'relative', - serverUrl: args.serverUrl, + serverUrl: args['server-url'], theme, filter: args.filter, sequence: args.seq, chromiumSandbox: args['chromium-sandbox'], } satisfies PngExportArgs, )packages/language-server/src/documentation/documentation-provider.ts (1)
71-72: Consider refactoring the if-chain toswitch(true).
This aligns the block with the preferred control-flow style.♻️ Suggested refactor
- if (ast.isDeploymentNode(node)) { - const el = parser.parseDeploymentNode(node) - return `**${el.title}**` - } - - if (ast.isDeployedInstance(node)) { - const instance = parser.parseDeployedInstance(node) - const [projectId, fqn] = FqnRef.isImportRef(instance.element) - ? [instance.element.project as ProjectId, instance.element.model as Fqn] - : [doc.likec4ProjectId, instance.element.model as Fqn] - const found = projectId ? this.locator.getParsedElement(fqn, projectId) : this.locator.getParsedElement(fqn) - const lines = [ - `_instance of_ \`${fqn}\``, - ] - if (found) { - lines.push(`**${found.element.title}**`) - } - return lines.join(' \n') - } - - if (ast.isElement(node)) { - const el = parser.parseElement(node) - if (!el) { - return - } - const lines = [ - `**${el.title}**`, - `<small>kind: \`${el.kind}\`</small>`, - ] - return lines.join(' \n') - } - - // Exhaustiveness check — errors at compile time if guard admits a new node type without a handler - const _exhaustive: never = node + switch (true) { + case ast.isDeploymentNode(node): { + const el = parser.parseDeploymentNode(node) + return `**${el.title}**` + } + case ast.isDeployedInstance(node): { + const instance = parser.parseDeployedInstance(node) + const [projectId, fqn] = FqnRef.isImportRef(instance.element) + ? [instance.element.project as ProjectId, instance.element.model as Fqn] + : [doc.likec4ProjectId, instance.element.model as Fqn] + const found = projectId ? this.locator.getParsedElement(fqn, projectId) : this.locator.getParsedElement(fqn) + const lines = [ + `_instance of_ \`${fqn}\``, + ] + if (found) { + lines.push(`**${found.element.title}**`) + } + return lines.join(' \n') + } + case ast.isElement(node): { + const el = parser.parseElement(node) + if (!el) { + return + } + const lines = [ + `**${el.title}**`, + `<small>kind: \`${el.kind}\`</small>`, + ] + return lines.join(' \n') + } + default: { + // Exhaustiveness check — errors at compile time if guard admits a new node type without a handler + const _exhaustive: never = node + return _exhaustive + } + }As per coding guidelines, "Favor switch(true) over if-else chains".
packages/likec4/src/cli/export/png/takeScreenshot.ts (1)
23-27: Consider refining the JSDoc for accuracy.The added JSDoc follows the coding guideline requirement but could be more precise:
- The
@param paramsdocuments a parameter that doesn't exist in the signature (the function uses destructuring).- The
@returnsdescription mentions "or failed after maxAttempts" but the function always resolves—it returns only the successful captures as an array of{ view, path }objects.Based on learnings, use JSDoc to document public classes and methods.
📝 Suggested refinement
/** * Capture each view as PNG under the output directory using the provided browser context. - * `@param` params - Browser context, views, output path, logger, timeout, and options (outputType, dynamicVariant, theme). - * `@returns` Promise resolving when all views are captured (or failed after maxAttempts). + * `@param` params - Configuration object containing browser context, views, output path, logger, timeout, and rendering options. + * `@returns` Promise resolving to an array of successfully captured views with their file paths. */Alternatively, document each destructured property individually:
/** * Capture each view as PNG under the output directory using the provided browser context. - * `@param` params - Browser context, views, output path, logger, timeout, and options (outputType, dynamicVariant, theme). - * `@returns` Promise resolving when all views are captured (or failed after maxAttempts). + * `@param` params.browserContext - The Playwright browser context to use for rendering + * `@param` params.views - Non-empty array of diagram views to capture + * `@param` params.output - Output directory path for PNG files + * `@param` params.logger - Logger instance for progress and error reporting + * `@param` params.timeout - Timeout in milliseconds for page operations + * `@param` params.maxAttempts - Maximum number of retry attempts per view + * `@param` params.dynamicVariant - Optional variant for dynamic views (e.g., 'sequence') + * `@param` params.outputType - Output path strategy: 'relative' preserves source structure, 'flat' outputs all to root + * `@param` params.theme - Theme to use for rendering ('light' or 'dark') + * `@returns` Promise resolving to an array of successfully captured views with their output file paths */packages/likec4/src/cli/index.ts (1)
111-115: Minor redundancy in exit handling.Setting
process.exitCode = 1beforeexit(1)is redundant sinceexit(1)explicitly uses the provided code. However, this is harmless and may be intentional for defensive coding.♻️ Optional simplification
function exitWithFailure(err: unknown, prefix?: string): never { - process.exitCode = 1 console.error(prefix != null ? `${prefix} ${loggable(err)}` : loggable(err)) exit(1) }packages/likec4/src/cli/export/drawio/handler.ts (1)
136-144: Consider type-safe view ID extraction.The cast
vm.$view.id as stringbypasses type safety. If the view ID type is alreadystring, the cast is redundant; if it's a branded type (e.g.,ViewId), the cast could hide type mismatches downstream.Suggested approach
If the type system guarantees
vm.$view.idis string-compatible, consider usingString(vm.$view.id)for explicit conversion, or verify the underlying type to remove the cast entirely.e2e/E2E-COVERAGE.md (1)
1-32: Consider translating documentation to English for consistency.The E2E coverage documentation is written in Portuguese while the rest of the codebase (code comments, other docs, commit messages) appears to use English. For consistency and broader team accessibility, consider translating this to English.
e2e/src/likec4-cli-export-drawio.spec.ts (1)
23-37: Document the skip reason in the test description or add a tracking issue.The test is skipped with a detailed comment explaining the CI issue, but this information is only visible when reading the source. Consider:
- Including a link to a tracking issue if one exists
- Adding the skip reason to the test name for visibility in test reports
Example
-test.skip( - 'LikeC4 CLI - export drawio produces .drawio file with mxfile', +test.skip( + 'LikeC4 CLI - export drawio produces .drawio file with mxfile (SKIPPED: CI tgz path resolution issue)',Or create a GitHub issue and reference it:
// TODO(`#123`): Re-enable when CI tgz path resolution is fixed test.skip(
… workspace) Co-authored-by: Cursor <[email protected]>
…ctors/specs, parse-drawio metaObj, icons revert postpack, worker switch(true), docs JSDoc, logger debug, tsdown copyPreview, vscode utils/types, log destructure, stage-exclude it.todo, config filenames JSDoc, HoverProvider @param/@returns, json/png/drawio handlers, E2E-COVERAGE en, test skip reason) Co-authored-by: Cursor <[email protected]>
…x ESM and duplicate test runs) Co-authored-by: Cursor <[email protected]>
Co-authored-by: Cursor <[email protected]>
…lve (avoid CJS .js) Co-authored-by: Cursor <[email protected]>
… .js with JSX Co-authored-by: Cursor <[email protected]>
…uild picking JSX-in-js); ignore diagram/src/**/*.js Co-authored-by: Cursor <[email protected]>
Co-authored-by: Cursor <[email protected]>
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/config/src/filenames.ts (1)
52-60:⚠️ Potential issue | 🟠 MajorType predicates are unsound when full paths are accepted.
isLikeC4NonJsonConfigandisLikeC4Configuse type predicates that narrow the input string to specific filename literals, but they accept full paths and extract the basename internally. This creates a type safety hole: if code callsisLikeC4Config('/path/likec4.config.ts'), TypeScript narrows the type to'likec4.config.ts', which is incorrect. Align both functions withisLikeC4JsonConfigby returningbooleaninstead of using type predicates.Suggested fix
-export function isLikeC4NonJsonConfig(filename: string): filename is typeof configNonJsonFilenames[number] { +export function isLikeC4NonJsonConfig(filename: string): boolean { return (configNonJsonFilenames as readonly string[]).includes(basename(filename)) } @@ -export function isLikeC4Config(filename: string): filename is typeof ConfigFilenames[number] { +export function isLikeC4Config(filename: string): boolean { return isLikeC4JsonConfig(filename) || isLikeC4NonJsonConfig(filename) }
🧹 Nitpick comments (8)
packages/vscode/src/utils.ts (1)
15-33: Consider sharing the performance mark helper to avoid drift.
There’s a very similarperformanceMark()inpackages/language-server/src/utils/index.ts; if package boundaries allow, centralizing the logic could prevent subtle divergence.packages/diagram/vite.config.ts (1)
164-165: Consider adding the same explanatory comment for consistency.The
bundleConfiguses the sameextensionsarray asdefaultConfig, but lacks the comment explaining why. Adding a similar comment (or extracting the array to a shared constant) would improve maintainability.♻️ Option 1: Add comment
resolve: { + // Prefer .ts/.tsx over .js so build uses source (avoid CJS .js with JSX in src/) extensions: ['.ts', '.tsx', '.mts', '.mjs', '.js', '.jsx', '.json'],♻️ Option 2: Extract to shared constant
+// Prefer .ts/.tsx over .js so build uses source (avoid CJS .js with JSX in src/) +const resolveExtensions = ['.ts', '.tsx', '.mts', '.mjs', '.js', '.jsx', '.json'] + const defaultConfig = defineConfig({ // ... resolve: { conditions: ['sources'], - // Prefer .ts/.tsx over .js so build uses source (avoid CJS .js with JSX in src/) - extensions: ['.ts', '.tsx', '.mts', '.mjs', '.js', '.jsx', '.json'], + extensions: resolveExtensions, // ... }, // ... }) const bundleConfig = defineConfig({ // ... resolve: { - extensions: ['.ts', '.tsx', '.mts', '.mjs', '.js', '.jsx', '.json'], + extensions: resolveExtensions, // ... }, // ... })packages/likec4/src/logger.ts (1)
66-70:hasErrorLoggedshould also be omitted for the optional override to take effect.The JSDoc states the intent is Vite compatibility. Currently, only
'debug'is omitted before re-adding bothdebugandhasErrorLoggedas optional. However,hasErrorLoggedfromReturnType(which is required) intersected with the optional version still results in a required property due to TypeScript intersection semantics.If the intent is for both to be optional:
♻️ Proposed fix
-export type ViteLogger = Omit<ReturnType<typeof createLikeC4Logger>, 'debug'> & { +export type ViteLogger = Omit<ReturnType<typeof createLikeC4Logger>, 'debug' | 'hasErrorLogged'> & { debug?: (msg: string, ...args: unknown[]) => void hasErrorLogged?: (error: Error | RollupError) => boolean }apps/playground/src/monaco/LanguageClientSync.tsx (1)
112-144: Consider adding minimal error logging for debugging.The silent
catchblocks at lines 123-124 and 136-137 make debugging difficult if issues arise. While the errors are non-critical for the Draw.io export feature, adding a debug-level log would help during development.✨ Optional: Add debug logging
try { const { model } = await c.sendRequest(FetchLayoutedModel.req, {}) return model ?? null } catch { + if (import.meta.env.DEV) logger.debug`getLayoutedModel request failed` return null }try { const res = await requestLayoutView(c, viewId as ViewId) if (res.result?.diagram) out[viewId] = res.result.diagram } catch { + if (import.meta.env.DEV) logger.debug`layoutViews failed for ${viewId}` // skip failed view }scripts/ci-validate-in-docker.sh (1)
16-18: Prefer a frozen lockfile for CI reproducibility.For CI-like validation, consider
--frozen-lockfileto avoid drift and ensure deterministic installs.♻️ Proposed change
-echo "==> Installing dependencies..." -pnpm install --prefer-offline +echo "==> Installing dependencies..." +pnpm install --prefer-offline --frozen-lockfilescripts/ci-validate-in-docker.ps1 (1)
9-9: Use Write-Output instead of Write-Host for compatibility.PSScriptAnalyzer flags Write-Host as non-redirectable; Write-Output is safer across hosts.
🔧 Proposed change
-Write-Host "==> Starting Linux container (node:22-bookworm) with workspace: $workspace" +Write-Output "==> Starting Linux container (node:22-bookworm) with workspace: $workspace"e2e/tests/drawio-playground.spec.ts (1)
45-50: Consider logging or commenting the intentional catch.The empty
.catch(() => {})on line 48 silently swallowsnetworkidletimeout errors. While this is a common pattern to avoid flaky tests when network-idle isn't strictly necessary, adding a brief comment would clarify the intent for future maintainers.💡 Suggested improvement
await page.goto(TUTORIAL_PATH) await page.waitForURL(/\/w\/tutorial\//) - await page.waitForLoadState('networkidle').catch(() => {}) + // networkidle is best-effort; tests can proceed once selector is ready + await page.waitForLoadState('networkidle').catch(() => {}) await page.waitForSelector(CANVAS_SELECTOR, { timeout: TIMEOUT_DIAGRAM })packages/generators/src/drawio/parse-drawio.ts (1)
671-677: Consider extracting a typed helper for filtered container cells.The non-null assertions (
cont.x!,cont.y!, etc.) are safe because the filter on lines 663-670 guarantees these properties exist. However, TypeScript's type narrowing doesn't propagate throughfilter(). A minor refinement would be to use a type guard or map to a narrower type.Optional: Type-safe container cell extraction
+type ContainerCellWithGeometry = DrawioCell & { + x: number + y: number + width: number + height: number +} + +function isContainerWithGeometry(v: DrawioCell): v is ContainerCellWithGeometry { + return ( + v.style?.toLowerCase().includes('container=1') === true && + v.x != null && + v.y != null && + v.width != null && + v.height != null + ) +} - const containerCells = vertices.filter( - v => - v.style?.toLowerCase().includes('container=1') && - v.x != null && - v.y != null && - v.width != null && - v.height != null, - ) + const containerCells = vertices.filter(isContainerWithGeometry) for (const cont of containerCells) { - const cx = cont.x!, - cy = cont.y!, - cw = cont.width!, - ch = cont.height! + const cx = cont.x, + cy = cont.y, + cw = cont.width, + ch = cont.height
Reaplica a feature de export Draw.io após revert no origin. Inclui as correções de code scanning (ReDoS em parse-drawio, sanitização em icons). PR para rodar CI no fork.
Made with Cursor
Summary by CodeRabbit
Release Notes
New Features
--roundtripflag--all-in-oneoption to combine multiple views into a single .drawio file--uncompressedexport option for improved compatibilityDocumentation
Tests