Skip to content

feat: edit view properties (title, description, tags)#2976

Merged
davydkov merged 23 commits into
mainfrom
viewEditing
May 22, 2026
Merged

feat: edit view properties (title, description, tags)#2976
davydkov merged 23 commits into
mainfrom
viewEditing

Conversation

@davydkov

Copy link
Copy Markdown
Member

Summary

  • New change-property view-change op in @likec4/core for editing view title, description, and tag (add/remove); applied as text edits by @likec4/language-server with formatting preserved
  • Streamline editor actor (split into actor/{machine,setup,actions,state.editor,state.sync-queue,types}.ts)
  • @likec4/generators: rename operatorsops, printprintOperation, printTabIndentprintWithTabIndent
  • @likec4/react: drop react-shadow, mark use-sync-external-store external, inline shadow-root styles
  • @likec4/diagram: fix overlay animations
  • Chore: upgrade to pnpm 11 (packageManager, .tool-versions); move overrides / allowBuilds / patchedDependencies into pnpm-workspace.yaml

Test plan

  • pnpm generate && pnpm typecheck
  • pnpm test (language-server viewChange suite green)
  • e2e screenshots refreshed and passing
  • Manual: edit a view's title/description/tags in the diagram and confirm round-trip into the .c4 source
  • Manual: shadow-root rendering in React/webcomponent embed still works after react-shadow removal

🤖 Generated with Claude Code

@changeset-bot

changeset-bot Bot commented May 22, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 30945a7

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

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

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

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

Comment thread packages/language-server/src/model-change/viewChange.spec.ts Fixed
Comment thread packages/language-server/src/model-change/viewChange.ts Fixed
Comment thread packages/language-server/src/model-change/viewChange.spec.ts Fixed
@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d785fbf7-699a-40f2-82e6-82fa13503acd

📥 Commits

Reviewing files that changed from the base of the PR and between 1a0710d and 30945a7.

📒 Files selected for processing (1)
  • packages/language-server/src/model-change/viewChange.spec.ts

📝 Walkthrough

Walkthrough

Adds change-property to ViewChange and implements CST-aware title/description/tag edits in the language server; refactors the diagram editor into typed XState actors with a sync queue; replaces react-shadow with a native ShadowRoot and updates overlays/search; renames generator printing APIs; upgrades pnpm/workspace and various build/tooling and tests.

Changes

Diagram runtime, editor actors, overlays, and shadow DOM

Layer / File(s) Summary
Editor actor core (types, machine, actions, sync queue)
packages/diagram/src/editor/actor/*
Adds typed Editor actor types, parallel editor+syncQueue state machines, actions for snapshots/history/sync queue, hotkey actor rename, and state configs.
Editor wiring and usage
packages/diagram/src/editor/*, .../useEditorActorLogic.ts, .../useEditorActor.ts, .../useDiagramCompareLayout.ts
Rewires imports to new actor modules, updates dispatched event types (edit.move.*, change.view, change.latest-to-manual), and updates undo/history guards.
LikeC4 UI wiring
packages/diagram/src/likec4diagram/*, LikeC4View.tsx, DiagramXYFlow.tsx
Selects child actors via snapshot, conditionally renders Overlays/Search with actor refs, adjusts navigation/overlay rendering and editing-edge detection.
Overlays and overlay actor
packages/diagram/src/overlays/*, overlaysActor.ts, Overlay.tsx
Refactors Overlay open/close lifecycle and backdrop/ESC handling; rewrites overlays state machine flow and close-handling guards.
Search UI and actor
packages/diagram/src/search/*, searchActor.ts
Search now accepts actor ref prop, uses AnimatePresence on columns, focuses results on open, sends animation.presence.end via onExitComplete, and adds timeout guard for animation-end in actor.
Shadow DOM and styles
packages/diagram/src/shadowroot/*, packages/diagram/src/custom/index.ts
Removes react-shadow, introduces native ShadowRoot host/portal, useShadowRoot hook, style/stylesheet creation utilities, and removes re-export of ShadowRoot.

Language server: change-property and tests

Layer / File(s) Summary
Core contract
packages/core/src/types/view-changes.ts
Adds ViewChange.ChangeProperty variant supporting title, description, and tag add/remove.
ModelChanges routing and applyEdits
packages/language-server/src/model-change/ModelChanges.ts
Intercepts change-property, calls preparePayload/changePropertyHandler, applies edits, and refreshes Langium documents in non-LSP fallback.
ViewChange handlers
packages/language-server/src/model-change/viewChange.ts
Implements preparePayload, changePropertyHandler, title/description insert/replace logic, and precise tag add/remove CST text edits.
Tests and mocks
packages/language-server/src/model-change/viewChange.spec.ts, src/__mocks__/*, src/test/testServices.ts
Adds comprehensive Vitest scenarios for title/description/tag edits using memfs-backed mocks and extended test-services context.
LS configs and pkg metadata
packages/language-server/*, tsconfig.json, vitest.config.ts
Adds dependency and reference updates, and adjusts vitest sequence/concurrency settings.

Generators API rename and tests

Layer / File(s) Summary
Generator API and implementation
packages/generators/src/likec4/generate-likec4.ts, .../index.ts
Renames operators→ops, replaces factory-based print APIs with printOperation/printWithTabIndent that take instantiated Ops, updates exports and docs.
Specs updated
packages/generators/src/likec4/generate-likec4.spec.ts
Tests adapted to new printOperation/printWithTabIndent usage.

Workspace, tooling, e2e, SPA, and misc manifests

Layer / File(s) Summary
Changesets and pnpm upgrade
.changeset/*, .tool-versions, pnpm-workspace.yaml, package.json
Adds changesets (overlay fix, API rename, react-shadow removal, pnpm 11 upgrade), bumps pnpm and migrates workspace config/catalog updates.
E2E and Playwright
e2e/*, e2e/playwright.config.ts
Switches zx globals usage, adds build-and-preview script, updates Playwright timeout and preview server command/port.
SPA build and entries
packages/likec4-spa/*, tsdown.config.mts, panda.config.ts
Adds codegen HTML bootstraps, updates tsdown entries/plugins, adjusts panda config, and updates local dev aliases.
Vite plugin HMR and react build
packages/vite-plugin/src/plugin.ts, packages/react/*
Adds batched onModelParsed handling, improved HMR reload/error signaling, and updates React build configs (vite/rolldown/babel/plugin changes).
Package manifests and catalogs
packages/*/package.json, patches/mnemonist@0.40.3.patch, pnpm-workspace.yaml
Multiple dependency/peer adjustments (remove react-shadow, add react-compiler-runtime/use-sync-external-store), tsdown catalog pinning, mnemonist ./patch export added.
Core utils tweaks
packages/core/src/utils/*
Switches mnemonist imports from patch entrypoint to main package surface.
Misc tests/docs packages/likec4-spa/README.md, small test reformatting and removed SSR mock export.

Estimated code review effort
🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • likec4/likec4#2904 — Modifies ModelChanges.applyChange/applyTextEdits fallback behavior (related to change-property edits).
  • likec4/likec4#2813 — Touches Playwright/webServer configuration and timeouts (related to e2e/playwright changes).
  • likec4/likec4#2967 — Related ShadowRoot/styles refactor and scoping changes.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch viewEditing

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/generators/src/likec4/generate-likec4.ts (1)

26-37: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update JSDoc examples to the renamed API (ops) and instantiated operations.

Examples still use operators.*, and one example passes an operator reference instead of an instantiated operation, which no longer matches the new public API.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/generators/src/likec4/generate-likec4.ts` around lines 26 - 37,
Update the JSDoc examples to use the renamed public API 'ops' instead of
'operators' and pass instantiated operations (call the operation factory) rather
than operator references; for example replace uses of operators.expression and
operators.model() with ops.expression() and ops.model() respectively, and adjust
the example that passed a ref to ensure it calls the operation factory so
printOperation receives an instantiated operation (refer to printOperation and
ops.* symbols in the diff).
packages/language-server/src/filesystem/LikeC4FileSystem.ts (1)

26-31: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Refine: WithFileSystem() default affects only direct no-arg usage; runtime watcher enablement is already explicit

File: packages/language-server/src/filesystem/LikeC4FileSystem.ts (Lines 26-31)

The current default (enableWatcher = false) only impacts callsites that invoke WithFileSystem() with no arguments. In this repo, production code paths pass an explicit boolean (e.g., startLanguageServer defaults enableWatcher to true and calls WithFileSystem(opts.enableWatcher)), and the only WithFileSystem() no-arg call found is in packages/language-server/src/model-change/viewChange.spec.ts (tests). Since WithFileSystem is exported publicly, changing the default would still break external consumers who call it directly with no args—update docs/semantics accordingly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/language-server/src/filesystem/LikeC4FileSystem.ts` around lines 26
- 31, Summary: WithFileSystem's default parameter (enableWatcher = false) only
affects callers who omit the arg (tests), but runtime callers already pass an
explicit boolean; exported API should not silently default and must be
clarified. Fix: remove the default value from the WithFileSystem signature so
callers must supply enableWatcher explicitly (change
"WithFileSystem(enableWatcher = false)" to "WithFileSystem(enableWatcher:
boolean)"), update the test(s) that call WithFileSystem() no-arg (e.g.,
viewChange.spec.ts) to pass an explicit boolean, and add a short JSDoc above
WithFileSystem explaining that watcher behavior is controlled by the
enableWatcher argument; reference the startLanguageServer callsite which already
passes opts.enableWatcher to verify callers are explicit.
🧹 Nitpick comments (7)
packages/core/src/types/view-changes.ts (1)

37-54: ⚡ Quick win

Require at least one property in change-property payload.

ChangeProperty currently permits { op: 'change-property' }, which creates a valid but meaningless operation. Tightening this contract prevents silent no-ops in downstream handlers.

Suggested type-shape tightening
 export namespace ViewChange {
+  type AtLeastOne<T> = {
+    [K in keyof T]-?: Required<Pick<T, K>> & Partial<Omit<T, K>>
+  }[keyof T]
+
-  export interface ChangeProperty {
-    op: 'change-property'
-    /**
-     * Change title
-     */
-    title?: string
-    /**
-     * Change description
-     */
-    description?: scalar.MarkdownOrString
-    /**
-     * Add or remove tags
-     */
-    tag?: {
-      add?: scalar.Tag | scalar.Tag[]
-      remove?: scalar.Tag | scalar.Tag[]
-    }
-  }
+  export type ChangeProperty = {
+    op: 'change-property'
+  } & AtLeastOne<{
+    /** Change title */
+    title: string
+    /** Change description */
+    description: scalar.MarkdownOrString
+    /** Add or remove tags */
+    tag: {
+      add?: scalar.Tag | scalar.Tag[]
+      remove?: scalar.Tag | scalar.Tag[]
+    }
+  }>
 }

Also applies to: 57-62

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/types/view-changes.ts` around lines 37 - 54, The
ChangeProperty interface allows an empty payload ({ op: 'change-property' }), so
tighten the type to require at least one of title, description, or tag be
present: replace the current optional-all interface with a discriminated union
or intersection that enforces at least one of { title }, { description }, or {
tag } (referencing ChangeProperty and the tag shape inside it) so consumers
cannot dispatch a no-op; apply the same non-empty requirement to the analogous
interface declared at the other location referenced (lines ~57-62).
packages/diagram/src/editor/actor/state.sync-queue.ts (2)

270-283: ⚡ Quick win

Remove commented-out code.

This substantial commented block appears to be unused exploration code. Consider removing it to improve readability, or if it represents planned functionality, track it in an issue instead.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/diagram/src/editor/actor/state.sync-queue.ts` around lines 270 -
283, Remove the large commented-out exploratory block that tries to find a
'save-view-snapshot' (the commented calls to find([...context.processing,
...context.syncQueue], p => !!p && isViewChange(p) && p.op ===
'save-view-snapshot') and the subsequent
enqueue.sendTo(typedSystem.diagramActor, { type: 'update.view-bounds', bounds:
snapshot.layout.bounds }) code); either delete it entirely from
state.sync-queue.ts to improve readability or move the logic into a tracked
issue if it represents planned work, leaving no commented implementation in the
file.

185-191: ⚡ Quick win

Use optional chaining for cleaner conditional.

The condition current && current._layout === 'manual' can be simplified with optional chaining.

♻️ Proposed fix
           input: ({ context }) => {
              const current = context.history?.head.change.layout
              return ({
-               current: current && current._layout === 'manual' ? current : undefined,
+               current: current?._layout === 'manual' ? current : undefined,
                viewId: context.viewId,
              })
            },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/diagram/src/editor/actor/state.sync-queue.ts` around lines 185 -
191, The conditional `current && current._layout === 'manual'` should use
optional chaining for clarity; in the input arrow function (the input: ({
context }) => { ... } block in state.sync-queue.ts) replace that expression with
`current?._layout === 'manual'` so the returned object sets current to current
when its _layout equals 'manual', otherwise undefined, leaving viewId unchanged.
packages/diagram/src/editor/actor/actions.ts (1)

83-99: 💤 Low value

Inconsistent action definition pattern.

pushHistory wraps machine.assign in an extra function, unlike saveBeforeEditing (line 67) which directly assigns. This creates an unnecessary closure and diverges from the pattern used elsewhere in this file.

♻️ Proposed fix for consistency
-export const pushHistory = () =>
-  machine.assign(({ context }) => {
+export const pushHistory = machine.assign(({ context }) => {
     const editing = context.editing
     if (import.meta.env.DEV) {
       console.log('pushHistory', editing)
     }
     if (editing) {
       return {
         editing: null,
         history: {
           head: editing.before,
           tail: context.history,
         },
       }
     }
     return {}
   })

Then update the call site in state.editor.ts from pushHistory() to pushHistory.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/diagram/src/editor/actor/actions.ts` around lines 83 - 99,
pushHistory currently returns a function that calls machine.assign, creating an
unnecessary closure and breaking the file's consistent action pattern (see
saveBeforeEditing which directly assigns). Change pushHistory to be the direct
result of machine.assign (i.e., export const pushHistory = machine.assign(...))
and update the call site in state.editor.ts to use pushHistory (no parentheses)
so the action is referenced consistently with other actions like
saveBeforeEditing.
packages/diagram/src/editor/actor/setup.ts (1)

2-8: 💤 Low value

Consider consolidating imports from the same module.

Multiple separate import statements from ./types could be combined into a single statement.

♻️ Proposed consolidation
-import { hotkey } from './hotkey'
-import type { EditorActorEvent } from './types'
-import type { EditorActorContext } from './types'
-import type { EditorActorEmitedEvent } from './types'
-import type { EditorActorInput } from './types'
-import type { EditorActorStateTag } from './types'
+import { hotkey } from './hotkey'
+import type {
+  EditorActorContext,
+  EditorActorEmitedEvent,
+  EditorActorEvent,
+  EditorActorInput,
+  EditorActorStateTag,
+} from './types'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/diagram/src/editor/actor/setup.ts` around lines 2 - 8, Multiple type
imports (EditorActorEvent, EditorActorContext, EditorActorEmitedEvent,
EditorActorInput, EditorActorStateTag) are imported separately from the same
module; consolidate them into a single import statement that imports all these
types together (keep the other import from 'xstate' as-is), updating the
existing import lines in setup.ts to use one combined import for the listed
EditorActor* types.
packages/diagram/src/search/Search.tsx (1)

130-139: 💤 Low value

Mark props as read-only for type safety.

Per static analysis, the props type should be marked as read-only to prevent accidental mutation.

🔧 Proposed fix
-function SearchOverlayBody({ searchActorRef }: { searchActorRef: SearchActorRef }) {
+function SearchOverlayBody({ searchActorRef }: Readonly<{ searchActorRef: SearchActorRef }>) {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/diagram/src/search/Search.tsx` around lines 130 - 139, The props for
SearchOverlayBody should be marked readonly to prevent mutation; update the
function signature for SearchOverlayBody so the parameter type uses a readonly
prop type (e.g., wrap the inline props type in Readonly<...> or declare a
Readonly interface) for the searchActorRef prop, ensuring the type references
SearchActorRef and that the JSX usage of SearchPanelContent and ref remain
unchanged.
packages/language-server/src/model-change/viewChange.spec.ts (1)

67-614: ⚡ Quick win

Add one idempotent/no-op change-property regression test.

Current coverage is great for mutating edits, but it should also assert behavior when the requested property change produces no text edit (e.g., same title/description or removing a non-existing tag).

As per coding guidelines, “Aim to cover new features with relevant tests; keep test names descriptive.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/language-server/src/model-change/viewChange.spec.ts` around lines 67
- 614, Add a new test under the existing "change-property" describe that
verifies idempotent/no-op edits: use testDoc to create a fixture, capture the
initial read(), then call change(...) (the change helper) with a no-op
change-property (e.g., setting title to the same value and/or removing a tag
that isn't present) and assert that read() is strictly unchanged and that no
text edits occurred; name the test something like "should be idempotent for
no-op changes" and place it alongside the other it(...) cases in the viewChange
spec.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.changeset/generators-api-rename.md:
- Around line 2-9: The changeset currently declares a patch release but lists
breaking public API renames (operators → ops, print → printOperation,
printTabIndent → printWithTabIndent) which require a non-patch bump; update the
release type from "patch" to at least "minor" (or "major" per your policy) in
the changeset so the package '`@likec4/generators`' is published with the
appropriate breaking-release level for the renamed symbols.

In `@packages/diagram/src/editor/actor/actions.ts`:
- Around line 154-161: The length check uses loose equality; update the
condition in the block that checks syncQueue.length (the if that currently reads
"if (syncQueue.length == 0)") to use strict equality (`===`) so it becomes "if
(syncQueue.length === 0)"; ensure the surrounding logic that returns {
syncQueue: [nextOp] } and the dev console.log remain unchanged and still
reference nextOp.

In `@packages/diagram/src/overlays/overlay/Overlay.tsx`:
- Around line 166-171: The backdrop click detection in the onClick handler of
Overlay.tsx currently uses a cast ((e.target as any)?.nodeName) — remove the
cast and replace the logic with a proper event target check (use e.target ===
e.currentTarget) so clicks on the dialog backdrop trigger cancelMe() while
clicks on content do not; update the onClick closure where cancelMe() is invoked
to use this comparison instead of nodeName inspection.

In `@packages/language-server/src/model-change/ModelChanges.ts`:
- Around line 24-44: The code currently calls changePropertyHandler and applies
its edits unconditionally; gate that call with a check that change.op ===
'change-property' (using the destructured change from changeView) so only
property-change operations run changePropertyHandler and applyTextEdits; if
change.op !== 'change-property' skip the handler and keep the invariant check
moved/adjusted so invariant(change.op !== 'change-property', ...) only runs when
you expect the operation to have been handled earlier (or remove the invariant
and instead early-return/continue for non-property ops). Ensure you reference
changePropertyHandler, change.op, preparePayload, applyTextEdits and invariant
in the updated control flow.

In `@packages/language-server/src/model-change/viewChange.ts`:
- Line 10: Remove the unused import named "space" from the import list in
viewChange.ts to satisfy linting; locate the import statement that includes
"space" (e.g., the import that currently lists "space,") and delete "space" from
that list (or remove the entire import line if it becomes empty) so no unused
symbol remains.
- Around line 259-267: The current handling of tag.add/tag.remove silently uses
only the first array element, causing dropped values; update the logic in
viewChange (around the code that calls addTag(viewAst, body, name) and
removeTag(body, name)) to explicitly handle arrays by iterating over tag.add and
tag.remove when Array.isArray(...) and invoking addTag/removeTag for each item
(or, if you prefer stricter behavior, validate and throw a descriptive error
when arrays are passed), ensuring every value is processed rather than only the
first.

In `@packages/likec4-spa/README.md`:
- Line 27: Update the README sentence that currently reads "To test webcomponent
(that one is generated from `likec4 gen`)" to use the two-word term "web
component" instead of "webcomponent" so the user-facing line becomes "To test
web component (that one is generated from `likec4 gen`)" — edit the README.md
content where that sentence appears.

---

Outside diff comments:
In `@packages/generators/src/likec4/generate-likec4.ts`:
- Around line 26-37: Update the JSDoc examples to use the renamed public API
'ops' instead of 'operators' and pass instantiated operations (call the
operation factory) rather than operator references; for example replace uses of
operators.expression and operators.model() with ops.expression() and ops.model()
respectively, and adjust the example that passed a ref to ensure it calls the
operation factory so printOperation receives an instantiated operation (refer to
printOperation and ops.* symbols in the diff).

In `@packages/language-server/src/filesystem/LikeC4FileSystem.ts`:
- Around line 26-31: Summary: WithFileSystem's default parameter (enableWatcher
= false) only affects callers who omit the arg (tests), but runtime callers
already pass an explicit boolean; exported API should not silently default and
must be clarified. Fix: remove the default value from the WithFileSystem
signature so callers must supply enableWatcher explicitly (change
"WithFileSystem(enableWatcher = false)" to "WithFileSystem(enableWatcher:
boolean)"), update the test(s) that call WithFileSystem() no-arg (e.g.,
viewChange.spec.ts) to pass an explicit boolean, and add a short JSDoc above
WithFileSystem explaining that watcher behavior is controlled by the
enableWatcher argument; reference the startLanguageServer callsite which already
passes opts.enableWatcher to verify callers are explicit.

---

Nitpick comments:
In `@packages/core/src/types/view-changes.ts`:
- Around line 37-54: The ChangeProperty interface allows an empty payload ({ op:
'change-property' }), so tighten the type to require at least one of title,
description, or tag be present: replace the current optional-all interface with
a discriminated union or intersection that enforces at least one of { title }, {
description }, or { tag } (referencing ChangeProperty and the tag shape inside
it) so consumers cannot dispatch a no-op; apply the same non-empty requirement
to the analogous interface declared at the other location referenced (lines
~57-62).

In `@packages/diagram/src/editor/actor/actions.ts`:
- Around line 83-99: pushHistory currently returns a function that calls
machine.assign, creating an unnecessary closure and breaking the file's
consistent action pattern (see saveBeforeEditing which directly assigns). Change
pushHistory to be the direct result of machine.assign (i.e., export const
pushHistory = machine.assign(...)) and update the call site in state.editor.ts
to use pushHistory (no parentheses) so the action is referenced consistently
with other actions like saveBeforeEditing.

In `@packages/diagram/src/editor/actor/setup.ts`:
- Around line 2-8: Multiple type imports (EditorActorEvent, EditorActorContext,
EditorActorEmitedEvent, EditorActorInput, EditorActorStateTag) are imported
separately from the same module; consolidate them into a single import statement
that imports all these types together (keep the other import from 'xstate'
as-is), updating the existing import lines in setup.ts to use one combined
import for the listed EditorActor* types.

In `@packages/diagram/src/editor/actor/state.sync-queue.ts`:
- Around line 270-283: Remove the large commented-out exploratory block that
tries to find a 'save-view-snapshot' (the commented calls to
find([...context.processing, ...context.syncQueue], p => !!p && isViewChange(p)
&& p.op === 'save-view-snapshot') and the subsequent
enqueue.sendTo(typedSystem.diagramActor, { type: 'update.view-bounds', bounds:
snapshot.layout.bounds }) code); either delete it entirely from
state.sync-queue.ts to improve readability or move the logic into a tracked
issue if it represents planned work, leaving no commented implementation in the
file.
- Around line 185-191: The conditional `current && current._layout === 'manual'`
should use optional chaining for clarity; in the input arrow function (the
input: ({ context }) => { ... } block in state.sync-queue.ts) replace that
expression with `current?._layout === 'manual'` so the returned object sets
current to current when its _layout equals 'manual', otherwise undefined,
leaving viewId unchanged.

In `@packages/diagram/src/search/Search.tsx`:
- Around line 130-139: The props for SearchOverlayBody should be marked readonly
to prevent mutation; update the function signature for SearchOverlayBody so the
parameter type uses a readonly prop type (e.g., wrap the inline props type in
Readonly<...> or declare a Readonly interface) for the searchActorRef prop,
ensuring the type references SearchActorRef and that the JSX usage of
SearchPanelContent and ref remain unchanged.

In `@packages/language-server/src/model-change/viewChange.spec.ts`:
- Around line 67-614: Add a new test under the existing "change-property"
describe that verifies idempotent/no-op edits: use testDoc to create a fixture,
capture the initial read(), then call change(...) (the change helper) with a
no-op change-property (e.g., setting title to the same value and/or removing a
tag that isn't present) and assert that read() is strictly unchanged and that no
text edits occurred; name the test something like "should be idempotent for
no-op changes" and place it alongside the other it(...) cases in the viewChange
spec.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 70a205a4-5eac-4d60-879c-4a42108bab1b

📥 Commits

Reviewing files that changed from the base of the PR and between 278d112 and 14283cc.

⛔ Files ignored due to path filters (18)
  • e2e/tests/__screenshots__/chromium-darwin/e2e-amazon-lambdas.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-amazon-rds.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-amazon-sqs.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-amazon.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-backend.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-cloud-legacy-backend.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-cloud-legacy.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-cloud-next.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-cloud-to-amazon-sequence.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-cloud.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-deploy-1.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-dynamic-view-1.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-graphql.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-index.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-multiple-expanded.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-multiple-explicit.png is excluded by !**/*.png
  • e2e/tests/__screenshots__/chromium-darwin/e2e-multiple-merged.png is excluded by !**/*.png
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (92)
  • .changeset/fix-overlay-animations.md
  • .changeset/generators-api-rename.md
  • .changeset/react-shadow-removed.md
  • .changeset/upgrade-pnpm-11.md
  • .tool-versions
  • devops/package.json
  • e2e/bootstrap.mjs
  • e2e/package.json
  • e2e/playwright.config.ts
  • e2e/pnpm-workspace.yaml
  • package.json
  • packages/core/src/types/view-changes.ts
  • packages/core/src/utils/mnemonist.ts
  • packages/core/src/utils/set.ts
  • packages/diagram/package.json
  • packages/diagram/src/LikeC4View.tsx
  • packages/diagram/src/adhoc-editor/ElementsTree.tsx
  • packages/diagram/src/custom/index.ts
  • packages/diagram/src/editor/actor/actions.ts
  • packages/diagram/src/editor/actor/hotkey.ts
  • packages/diagram/src/editor/actor/machine.ts
  • packages/diagram/src/editor/actor/setup.ts
  • packages/diagram/src/editor/actor/state.editor.ts
  • packages/diagram/src/editor/actor/state.sync-queue.ts
  • packages/diagram/src/editor/actor/types.ts
  • packages/diagram/src/editor/editorActor.actions.ts
  • packages/diagram/src/editor/editorActor.states.ts
  • packages/diagram/src/editor/index.ts
  • packages/diagram/src/editor/useEditorActorLogic.ts
  • packages/diagram/src/hooks/useDiagramCompareLayout.ts
  • packages/diagram/src/hooks/useEditorActor.ts
  • packages/diagram/src/hooks/useLikeC4ElementsTree.ts
  • packages/diagram/src/likec4diagram/DiagramUI.tsx
  • packages/diagram/src/likec4diagram/DiagramXYFlow.tsx
  • packages/diagram/src/likec4diagram/state/DiagramActorProvider.tsx
  • packages/diagram/src/likec4diagram/state/diagram-api.ts
  • packages/diagram/src/likec4diagram/state/machine.actions.ts
  • packages/diagram/src/likec4diagram/state/machine.setup.ts
  • packages/diagram/src/likec4diagram/state/machine.state.ready.ts
  • packages/diagram/src/likec4diagram/state/machine.ts
  • packages/diagram/src/likec4diagram/state/types.ts
  • packages/diagram/src/likec4diagram/state/utils.ts
  • packages/diagram/src/navigationpanel/editorpanel/ApplySemanticLayout.tsx
  • packages/diagram/src/overlays/overlay/Overlay.tsx
  • packages/diagram/src/overlays/overlaysActor.ts
  • packages/diagram/src/overlays/relationships-browser/SelectElement.tsx
  • packages/diagram/src/search/Search.tsx
  • packages/diagram/src/search/components/ElementsColumn.tsx
  • packages/diagram/src/search/searchActor.ts
  • packages/diagram/src/shadowroot/ShadowRoot.tsx
  • packages/diagram/src/shadowroot/styles.css.ts
  • packages/generators/src/likec4/generate-likec4.spec.ts
  • packages/generators/src/likec4/generate-likec4.ts
  • packages/generators/src/likec4/index.ts
  • packages/language-server/package.json
  • packages/language-server/src/__mocks__/fs.cjs
  • packages/language-server/src/__mocks__/fs/promises.cjs
  • packages/language-server/src/filesystem/LikeC4FileSystem.ts
  • packages/language-server/src/model-change/ModelChanges.ts
  • packages/language-server/src/model-change/viewChange.spec.ts
  • packages/language-server/src/model-change/viewChange.ts
  • packages/language-server/src/test/testServices.ts
  • packages/language-server/src/validation/specification.spec.ts
  • packages/language-server/tsconfig.json
  • packages/language-server/vitest.config.ts
  • packages/likec4-spa/README.md
  • packages/likec4-spa/codegen/react.html
  • packages/likec4-spa/codegen/webcomponent.html
  • packages/likec4-spa/codegen/webcomponent.tsx
  • packages/likec4-spa/package.json
  • packages/likec4-spa/panda.config.ts
  • packages/likec4-spa/src/page-title.spec.ts
  • packages/likec4-spa/start-dev.ts
  • packages/likec4-spa/tsconfig.src.json
  • packages/likec4-spa/tsdown.config.mts
  • packages/likec4/package.json
  • packages/log/package.json
  • packages/lsp/package.json
  • packages/mcp/package.json
  • packages/react/package.json
  • packages/react/panda.config.mjs
  • packages/react/src/react-dom-server-mock.ts
  • packages/react/tsconfig.json
  • packages/react/vite.config.mjs
  • packages/vite-plugin/package.json
  • packages/vite-plugin/src/plugin.ts
  • packages/vscode/package.json
  • packages/vscode/tsdown.config.ts
  • patches/mnemonist@0.40.3.patch
  • pnpm-workspace.yaml
  • styled-system/styles/panda.config.ts
  • vitest.config.ts
💤 Files with no reviewable changes (7)
  • packages/diagram/src/custom/index.ts
  • packages/react/src/react-dom-server-mock.ts
  • packages/diagram/src/likec4diagram/state/DiagramActorProvider.tsx
  • packages/diagram/src/editor/editorActor.actions.ts
  • patches/mnemonist@0.40.3.patch
  • packages/language-server/vitest.config.ts
  • packages/diagram/src/editor/editorActor.states.ts

Comment thread .changeset/generators-api-rename.md
Comment thread packages/diagram/src/editor/actor/actions.ts Outdated
Comment thread packages/diagram/src/overlays/overlay/Overlay.tsx
Comment thread packages/language-server/src/model-change/ModelChanges.ts
Comment thread packages/language-server/src/model-change/viewChange.ts Outdated
Comment thread packages/language-server/src/model-change/viewChange.ts Outdated
Comment thread packages/likec4-spa/README.md Outdated
davydkov added 2 commits May 22, 2026 15:22
- ModelChanges: gate change-property handler behind matching op
- viewChange: handle tag.add/tag.remove arrays fully instead of dropping all but the first entry; drop unused space import
- viewChange.spec: drop unused afterAll import and fs destructure
- Overlay: use e.target === e.currentTarget for backdrop click (removes `as any`)
- editor/actions: use strict equality on syncQueue.length
- editor/setup: consolidate type imports from ./types
- editor/state.sync-queue: drop dead commented block; use optional chaining
- generators: update JSDoc examples to renamed ops API
- likec4-spa README: "webcomponent" -> "web component"
Adds three tests for the change-property handler:
- add multiple tags from an array
- remove multiple tags from an array
- add and remove tags in the same change
@davydkov davydkov merged commit 783155b into main May 22, 2026
18 checks passed
@davydkov davydkov deleted the viewEditing branch May 22, 2026 13:39
@likec4-ci likec4-ci Bot mentioned this pull request May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant