Skip to content

fix: simplify ensureCorrectLayoutScale and fix link sync during Vue node drag#9680

Merged
DrJKL merged 28 commits intomainfrom
drjkl/dark-energy
Mar 13, 2026
Merged

fix: simplify ensureCorrectLayoutScale and fix link sync during Vue node drag#9680
DrJKL merged 28 commits intomainfrom
drjkl/dark-energy

Conversation

@DrJKL
Copy link
Contributor

@DrJKL DrJKL commented Mar 9, 2026

Summary

Fix node layout drift from repeated ensureCorrectLayoutScale scaling, simplify it to a pure one-time normalizer, and fix links not following Vue nodes during drag.

Changes

  • What:
    • ensureCorrectLayoutScale simplified to a one-time normalizer: unprojects legacy Vue-scaled coordinates back to canonical LiteGraph coordinates, marks the graph as corrected, and does nothing else. No longer touches the layout store, syncs reroutes, or changes canvas scale.
    • Removed no-op calls from useVueNodeLifecycle.ts (a renderer version string was passed where an LGraph was expected).
    • layoutStore.finalizeOperation now calls notifyChange synchronously instead of via setTimeout. This ensures useLayoutSync's onChange callback pushes positions to LiteGraph node.pos and calls canvas.setDirty() within the same RAF frame as a drag update, fixing links not following Vue nodes during drag.
  • Tests: Added tests for ensureCorrectLayoutScale (idempotency, round-trip, unknown-renderer no-op) and graphRenderTransform (project/unproject round-trips, anchor caching).

Review Focus

  • The setTimeout(() => this.notifyChange(change), 0)this.notifyChange(change) change in layoutStore.ts is the key fix for the drag-link-sync bug. The listener (useLayoutSync) only writes to LiteGraph, not back to the layout store, so synchronous notification is safe.
  • ensureCorrectLayoutScale no longer has any side effects beyond normalizing coordinates and setting workflowRendererVersion metadata.

…drift

Prefer graph.extra.workflowRendererVersion over the caller-supplied renderer parameter. After scaling sets this metadata, subsequent calls see the graph already matches and return early.

Remove default 'LG' fallback — if renderer is unknown and graph has no metadata, do nothing instead of assuming LG and scaling.

Amp-Thread-ID: https://ampcode.com/threads/T-019cd488-7093-77ab-b91f-01b2009351b9
Co-authored-by: Amp <amp@ampcode.com>
@DrJKL DrJKL requested a review from a team as a code owner March 9, 2026 22:18
@dosubot dosubot bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Mar 9, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cf930dde-ff77-4f66-953f-06678ca34771

📥 Commits

Reviewing files that changed from the base of the PR and between 8842d76 and d157906.

📒 Files selected for processing (1)
  • browser_tests/tests/rendererToggleStability.spec.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • browser_tests/tests/rendererToggleStability.spec.ts

📝 Walkthrough

Walkthrough

One-time graph-wide normalization converts Vue-mutated coordinates back to canonical LiteGraph coordinates; layout store adds synchronous per-node listeners and batched global notifications; layout sync batches writes via microtask/RAF and exposes start/stop; node dragging/resizing use batched move/update mutations; many unit and E2E tests added.

Changes

Cohort / File(s) Summary
Coordinate Transform Module
src/renderer/core/layout/transform/graphRenderTransform.ts, src/renderer/core/layout/transform/graphRenderTransform.test.ts
Adds RENDER_SCALE_FACTOR, MIN_NODE_WIDTH, anchor caching, and unproject/project helpers; tests for round-trip and anchor caching.
Layout Normalization
src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts, src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.test.ts
Implements one-time graph-wide unprojection flow (nodes, reroutes, IOs, groups) using a shared anchor and marks graphs 'Vue-corrected'; removes feature-flag/canvas-driven mutations.
Layout Store Enhancements
src/renderer/core/layout/store/layoutStore.ts, src/renderer/core/layout/store/layoutStore.test.ts, src/renderer/core/layout/types.ts
Adds onNodeChange(nodeId, cb) with synchronous node-scoped notifications, introduces nodeChangeListeners, queued global dispatch (batched flush), and tests validating sync vs deferred behavior.
Layout Sync & Batching
src/renderer/core/layout/sync/useLayoutSync.ts, src/renderer/core/layout/sync/useLayoutSync.test.ts
Batches node updates into pendingNodeIds, schedules flush via microtask or RAF, applies writes to LiteGraph nodes in a single flush; exposes startSync/stopSync.
Layout Mutations & Dragging
src/renderer/core/layout/operations/layoutMutations.ts, src/renderer/extensions/vueNodes/layout/useNodeDrag.ts, src/renderer/extensions/vueNodes/layout/useNodeDrag.test.ts
Adds batchMoveNodes(updates) and refactors drag flows to collect and apply a single batched move transaction; tests assert batching and avoid individual moveNode calls.
Vue Node Resize Tracking & Resize
src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts, src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.test.ts, src/renderer/extensions/vueNodes/interactions/resize/useNodeResize.ts, src/renderer/extensions/vueNodes/interactions/resize/useNodeResize.test.ts
Introduces fresh-measurement caching, collapsed-node handling, normalized measurements, and caching to avoid redundant writes; replaces hardcoded min width with MIN_NODE_WIDTH.
Vue Node Components & Slot Tracking
src/renderer/extensions/vueNodes/components/LGraphNode.vue, src/renderer/extensions/vueNodes/composables/useSlotElementTracking.ts, src/renderer/extensions/vueNodes/composables/useSlotElementTracking.test.ts
Moves min-width to CSS var via MIN_NODE_WIDTH, subscribes to per-node onNodeChange, introduces createSlotLayout/isBoundsEqual, and skips slot updates when geometry unchanged.
Form Dropdown & Widgets
src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue, src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.test.ts
Short-circuits filtering when closed, adjusts computed deps, guards popover calls, simplifies mocks, and adds tests for open/closed filtering behavior.
Legacy Topbar & App Integration
src/components/TopMenuSection.vue, src/components/TopMenuSection.test.ts, src/scripts/app.ts, browser_tests/tests/rendererToggleStability.spec.ts
Debounces legacy content checks via RAF with cleanup; updates ensureCorrectLayoutScale call to pass root graph; adds E2E test toggling Vue/LG renderer to assert position stability.
Types & Schema
src/lib/litegraph/src/LGraph.ts, src/platform/workflow/validation/schemas/workflowSchema.ts
Extends RendererType and validation enum to include 'Vue-corrected'.

Sequence Diagram

sequenceDiagram
    rect rgba(200,220,255,0.5)
    participant UI as User/UI
    participant Sync as useLayoutSync
    participant Store as LayoutStore
    participant RAF as RAF/microtask
    participant Lite as LiteGraph
    end

    UI->>Sync: emit layout change (nodeId)
    activate Sync
    Sync->>Sync: collect pendingNodeIds
    Sync->>RAF: scheduleFlush (microtask or RAF)
    deactivate Sync

    Note over UI,RAF: multiple events coalesce

    RAF->>Sync: flushPendingChanges
    activate Sync
    loop for each pendingNodeId
        Sync->>Store: graph.getNodeById(nodeId)
        Store-->>Sync: liteNode
        Sync->>Lite: apply position & size updates
    end
    Sync->>Store: set source and flush batched writes
    Sync->>Sync: clear pendingNodeIds
    deactivate Sync

    alt Node-scoped listeners
        Store->>Store: notifyNodeChange(change) (synchronous)
        Store->>UI: invoke per-node callbacks
    else Global listeners
        Store->>Store: queueGlobalChange(change) (batched)
        Store->>Store: flushQueuedGlobalChanges() (microtask)
        Store->>UI: invoke global callbacks
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through anchors, scaled and neat,

Reprojected nodes to cozy seat.
Batches hum softly, per-node ears twitch,
Frames coalesce—no duplicate glitch.
A tiny rabbit cheers the switch!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the two main changes: simplifying ensureCorrectLayoutScale and fixing link sync during Vue node drag.
Description check ✅ Passed The description is well-structured with Summary, Changes, and Review Focus sections. It covers the what, why, and critical design decisions.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch drjkl/dark-energy
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link

github-actions bot commented Mar 9, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 03/13/2026, 08:03:30 AM UTC

Links

@github-actions
Copy link

github-actions bot commented Mar 9, 2026

🎭 Playwright: ✅ 546 passed, 0 failed · 8 flaky

📊 Browser Reports
  • chromium: View Report (✅ 533 / ❌ 0 / ⚠️ 8 / ⏭️ 10)
  • chromium-2x: View Report (✅ 2 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • chromium-0.5x: View Report (✅ 1 / ❌ 0 / ⚠️ 0 / ⏭️ 0)
  • mobile-chrome: View Report (✅ 10 / ❌ 0 / ⚠️ 0 / ⏭️ 0)

@github-actions
Copy link

github-actions bot commented Mar 9, 2026

📦 Bundle: 4.61 MB gzip 🔴 +1.41 kB

Details

Summary

  • Raw size: 21.6 MB baseline 21.6 MB — 🔴 +4.65 kB
  • Gzip: 4.61 MB baseline 4.61 MB — 🔴 +1.41 kB
  • Brotli: 3.56 MB baseline 3.56 MB — 🔴 +693 B
  • Bundles: 235 current • 235 baseline • 109 added / 109 removed

Category Glance
Graph Workspace 🔴 +2.42 kB (1.02 MB) · Data & Services 🔴 +2.13 kB (2.8 MB) · Other 🔴 +103 B (8.16 MB) · Vendor & Third-Party ⚪ 0 B (8.9 MB) · Panels & Settings ⚪ 0 B (441 kB) · Editors & Dialogs ⚪ 0 B (78.3 kB) · + 5 more

App Entry Points — 17.8 kB (baseline 17.8 kB) • ⚪ 0 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-Dx_ZaUJN.js (removed) 17.8 kB 🟢 -17.8 kB 🟢 -6.29 kB 🟢 -5.45 kB
assets/index-jEovvBn0.js (new) 17.8 kB 🔴 +17.8 kB 🔴 +6.29 kB 🔴 +5.46 kB

Status: 1 added / 1 removed

Graph Workspace — 1.02 MB (baseline 1.01 MB) • 🔴 +2.42 kB

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-fmwRF14v.js (new) 1.02 MB 🔴 +1.02 MB 🔴 +213 kB 🔴 +161 kB
assets/GraphView-dIX6plNu.js (removed) 1.01 MB 🟢 -1.01 MB 🟢 -213 kB 🟢 -161 kB

Status: 1 added / 1 removed

Views & Navigation — 72.5 kB (baseline 72.5 kB) • ⚪ 0 B

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-BW0ucipD.js (new) 15.5 kB 🔴 +15.5 kB 🔴 +3.32 kB 🔴 +2.82 kB
assets/CloudSurveyView-elUNhUUP.js (removed) 15.5 kB 🟢 -15.5 kB 🟢 -3.32 kB 🟢 -2.82 kB
assets/CloudLoginView-Db32cTL8.js (new) 11.5 kB 🔴 +11.5 kB 🔴 +3.21 kB 🔴 +2.85 kB
assets/CloudLoginView-eSishNdj.js (removed) 11.5 kB 🟢 -11.5 kB 🟢 -3.2 kB 🟢 -2.83 kB
assets/CloudSignupView-B_mCdZxO.js (removed) 9.41 kB 🟢 -9.41 kB 🟢 -2.71 kB 🟢 -2.4 kB
assets/CloudSignupView-w1CVUaA6.js (new) 9.41 kB 🔴 +9.41 kB 🔴 +2.71 kB 🔴 +2.38 kB
assets/UserCheckView-BoA7DMFa.js (removed) 8.42 kB 🟢 -8.42 kB 🟢 -2.22 kB 🟢 -1.94 kB
assets/UserCheckView-D3KCERtK.js (new) 8.42 kB 🔴 +8.42 kB 🔴 +2.23 kB 🔴 +1.94 kB
assets/CloudLayoutView-Bn_rPq5Z.js (removed) 6.56 kB 🟢 -6.56 kB 🟢 -2.17 kB 🟢 -1.87 kB
assets/CloudLayoutView-BOTr2XJ7.js (new) 6.56 kB 🔴 +6.56 kB 🔴 +2.17 kB 🔴 +1.88 kB
assets/CloudForgotPasswordView-D2PhBozb.js (new) 5.59 kB 🔴 +5.59 kB 🔴 +1.95 kB 🔴 +1.73 kB
assets/CloudForgotPasswordView-DJhwvhms.js (removed) 5.59 kB 🟢 -5.59 kB 🟢 -1.95 kB 🟢 -1.75 kB
assets/CloudAuthTimeoutView-C-l_6LW6.js (removed) 4.96 kB 🟢 -4.96 kB 🟢 -1.79 kB 🟢 -1.57 kB
assets/CloudAuthTimeoutView-ClhJ586n.js (new) 4.96 kB 🔴 +4.96 kB 🔴 +1.79 kB 🔴 +1.57 kB
assets/CloudSubscriptionRedirectView-BQQjTyF2.js (new) 4.78 kB 🔴 +4.78 kB 🔴 +1.8 kB 🔴 +1.6 kB
assets/CloudSubscriptionRedirectView-Dy1M_2WW.js (removed) 4.78 kB 🟢 -4.78 kB 🟢 -1.8 kB 🟢 -1.59 kB
assets/UserSelectView-B66ATxBt.js (new) 4.55 kB 🔴 +4.55 kB 🔴 +1.67 kB 🔴 +1.49 kB
assets/UserSelectView-BWqldZIN.js (removed) 4.55 kB 🟢 -4.55 kB 🟢 -1.67 kB 🟢 -1.49 kB
assets/CloudSorryContactSupportView-CZs--wLE.js 1.02 kB 1.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/layout-B0KvsxR-.js 296 B 296 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 9 added / 9 removed

Panels & Settings — 441 kB (baseline 441 kB) • ⚪ 0 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SecretsPanel--1bED-8l.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +5.29 kB 🔴 +4.64 kB
assets/SecretsPanel-BTKgqFmu.js (removed) 21.5 kB 🟢 -21.5 kB 🟢 -5.29 kB 🟢 -4.64 kB
assets/LegacyCreditsPanel-B7moFNjX.js (new) 20.7 kB 🔴 +20.7 kB 🔴 +5.59 kB 🔴 +4.92 kB
assets/LegacyCreditsPanel-B8Mr9NTg.js (removed) 20.7 kB 🟢 -20.7 kB 🟢 -5.58 kB 🟢 -4.92 kB
assets/SubscriptionPanel-DcVLCDI-.js (removed) 18.8 kB 🟢 -18.8 kB 🟢 -4.79 kB 🟢 -4.21 kB
assets/SubscriptionPanel-wftbM1t7.js (new) 18.8 kB 🔴 +18.8 kB 🔴 +4.79 kB 🔴 +4.2 kB
assets/KeybindingPanel-CkxCcbKy.js (removed) 15.2 kB 🟢 -15.2 kB 🟢 -4.1 kB 🟢 -3.65 kB
assets/KeybindingPanel-sRpIrQiK.js (new) 15.2 kB 🔴 +15.2 kB 🔴 +4.11 kB 🔴 +3.65 kB
assets/AboutPanel-6GXsQs5-.js (new) 11.4 kB 🔴 +11.4 kB 🔴 +3.19 kB 🔴 +2.87 kB
assets/AboutPanel-avpzmcli.js (removed) 11.4 kB 🟢 -11.4 kB 🟢 -3.19 kB 🟢 -2.87 kB
assets/ExtensionPanel-DbgCWNpA.js (new) 9.42 kB 🔴 +9.42 kB 🔴 +2.67 kB 🔴 +2.37 kB
assets/ExtensionPanel-Ed70-PBS.js (removed) 9.42 kB 🟢 -9.42 kB 🟢 -2.66 kB 🟢 -2.37 kB
assets/ServerConfigPanel-CWOQtDm9.js (new) 6.49 kB 🔴 +6.49 kB 🔴 +2.13 kB 🔴 +1.93 kB
assets/ServerConfigPanel-D-kethly.js (removed) 6.49 kB 🟢 -6.49 kB 🟢 -2.13 kB 🟢 -1.91 kB
assets/UserPanel-BlQoqtCR.js (new) 6.2 kB 🔴 +6.2 kB 🔴 +2.01 kB 🔴 +1.75 kB
assets/UserPanel-CHMd7DJn.js (removed) 6.2 kB 🟢 -6.2 kB 🟢 -2 kB 🟢 -1.76 kB
assets/cloudRemoteConfig-B13jaU3T.js (removed) 1.48 kB 🟢 -1.48 kB 🟢 -720 B 🟢 -625 B
assets/cloudRemoteConfig-cJcgwkuy.js (new) 1.48 kB 🔴 +1.48 kB 🔴 +726 B 🔴 +628 B
assets/refreshRemoteConfig-CzSjbbTG.js (new) 1.14 kB 🔴 +1.14 kB 🔴 +522 B 🔴 +458 B
assets/refreshRemoteConfig-D4Qm6etH.js (removed) 1.14 kB 🟢 -1.14 kB 🟢 -520 B 🟢 -477 B
assets/config-BvvCTN9L.js 1.22 kB 1.22 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BfQyLBDO.js 29.9 kB 29.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BqbQ2NxP.js 28.8 kB 28.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Bxm3tP8R.js 23.9 kB 23.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-C7XU5YOb.js 27.8 kB 27.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CcKRE9rR.js 30.5 kB 30.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CKawpnbl.js 27.9 kB 27.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CRfUBXVB.js 24.5 kB 24.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Db_XIkOr.js 32.4 kB 32.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DTRLf7Yk.js 34.2 kB 34.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-vE0CQKZh.js 28.7 kB 28.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-ZInMAnck.js 38.5 kB 38.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 10 added / 10 removed

User & Accounts — 16.1 kB (baseline 16.1 kB) • ⚪ 0 B

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/auth-CkzdGpO0.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.18 kB 🟢 -989 B
assets/auth-d3Ye5McM.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.18 kB 🔴 +989 B
assets/SignUpForm-Dw1roL6T.js (new) 3.01 kB 🔴 +3.01 kB 🔴 +1.23 kB 🔴 +1.09 kB
assets/SignUpForm-tPuU1Hur.js (removed) 3.01 kB 🟢 -3.01 kB 🟢 -1.22 kB 🟢 -1.09 kB
assets/UpdatePasswordContent-DyvTyxrD.js (removed) 2.41 kB 🟢 -2.41 kB 🟢 -1.09 kB 🟢 -959 B
assets/UpdatePasswordContent-iT7aleQv.js (new) 2.41 kB 🔴 +2.41 kB 🔴 +1.09 kB 🔴 +961 B
assets/firebaseAuthStore-BptNDvHI.js (removed) 831 B 🟢 -831 B 🟢 -406 B 🟢 -364 B
assets/firebaseAuthStore-BVCR2s01.js (new) 831 B 🔴 +831 B 🔴 +407 B 🔴 +361 B
assets/auth-CHf_EHCI.js (new) 357 B 🔴 +357 B 🔴 +224 B 🔴 +223 B
assets/auth-Csp9AErF.js (removed) 357 B 🟢 -357 B 🟢 -224 B 🟢 -216 B
assets/PasswordFields-Cf67SVrX.js 4.51 kB 4.51 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WorkspaceProfilePic-Btg7fxEx.js 1.59 kB 1.59 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

Editors & Dialogs — 78.3 kB (baseline 78.3 kB) • ⚪ 0 B

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useShareDialog-BAdtUYc-.js (removed) 77.5 kB 🟢 -77.5 kB 🟢 -16.6 kB 🟢 -14.2 kB
assets/useShareDialog-D_349lGn.js (new) 77.5 kB 🔴 +77.5 kB 🔴 +16.6 kB 🔴 +14.2 kB
assets/useSubscriptionDialog-COnIGZvs.js (new) 779 B 🔴 +779 B 🔴 +397 B 🔴 +341 B
assets/useSubscriptionDialog-ieHZXtHT.js (removed) 779 B 🟢 -779 B 🟢 -395 B 🟢 -342 B

Status: 2 added / 2 removed

UI Components — 56.7 kB (baseline 56.7 kB) • ⚪ 0 B

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/ComfyQueueButton-BX187I95.js (new) 13.8 kB 🔴 +13.8 kB 🔴 +3.9 kB 🔴 +3.48 kB
assets/ComfyQueueButton-CGas9l-P.js (removed) 13.8 kB 🟢 -13.8 kB 🟢 -3.9 kB 🟢 -3.48 kB
assets/useTerminalTabs-Dz4J6nF5.js (new) 9.87 kB 🔴 +9.87 kB 🔴 +3.41 kB 🔴 +3.02 kB
assets/useTerminalTabs-jqOV201-.js (removed) 9.87 kB 🟢 -9.87 kB 🟢 -3.41 kB 🟢 -3.01 kB
assets/SubscribeButton-BaR2aaqy.js (removed) 2.34 kB 🟢 -2.34 kB 🟢 -1.01 kB 🟢 -879 B
assets/SubscribeButton-Ce48TkIv.js (new) 2.34 kB 🔴 +2.34 kB 🔴 +1.01 kB 🔴 +880 B
assets/cloudFeedbackTopbarButton-C75AB4LI.js (removed) 1.42 kB 🟢 -1.42 kB 🟢 -744 B 🟢 -648 B
assets/cloudFeedbackTopbarButton-CIVCbW3H.js (new) 1.42 kB 🔴 +1.42 kB 🔴 +747 B 🔴 +651 B
assets/ComfyQueueButton-BgbyePy5.js (removed) 836 B 🟢 -836 B 🟢 -409 B 🟢 -365 B
assets/ComfyQueueButton-CKYS974v.js (new) 836 B 🔴 +836 B 🔴 +414 B 🔴 +365 B
assets/Button-CDsnTrw4.js 3.22 kB 3.22 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/CloudBadge-B4gOfrWd.js 1.11 kB 1.11 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/FormSearchInput-DzHgSBr8.js 3.73 kB 3.73 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ScrubableNumberInput-C9ckhZ64.js 6.11 kB 6.11 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/toggle-group-Dv_H1vc5.js 3.83 kB 3.83 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/TopbarBadge--TxN3ma0.js 7.39 kB 7.39 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/UserAvatar-Dyw7wJA9.js 1.19 kB 1.19 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetButton-B54s5Vpy.js 1.84 kB 1.84 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

Data & Services — 2.8 MB (baseline 2.79 MB) • 🔴 +2.13 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-CdPonit_.js (removed) 1.95 MB 🟢 -1.95 MB 🟢 -438 kB 🟢 -328 kB
assets/dialogService-Dm3RpFzl.js (new) 1.95 MB 🔴 +1.95 MB 🔴 +439 kB 🔴 +328 kB
assets/api-BhhtE17C.js (new) 697 kB 🔴 +697 kB 🔴 +157 kB 🔴 +125 kB
assets/api-nhkdlL7E.js (removed) 695 kB 🟢 -695 kB 🟢 -157 kB 🟢 -125 kB
assets/load3dService-B21FpMCD.js (new) 91.1 kB 🔴 +91.1 kB 🔴 +19.1 kB 🔴 +16.4 kB
assets/load3dService-Bz9l8nJc.js (removed) 91.1 kB 🟢 -91.1 kB 🟢 -19.1 kB 🟢 -16.5 kB
assets/extensionStore-Cd_GKQ3L.js (removed) 13.6 kB 🟢 -13.6 kB 🟢 -4.63 kB 🟢 -4.1 kB
assets/extensionStore-EGylbD2Z.js (new) 13.6 kB 🔴 +13.6 kB 🔴 +4.63 kB 🔴 +4.09 kB
assets/workflowShareService-DPXsOkCC.js (new) 13.3 kB 🔴 +13.3 kB 🔴 +4.11 kB 🔴 +3.63 kB
assets/workflowShareService-pJkqJIxg.js (removed) 13.3 kB 🟢 -13.3 kB 🟢 -4.11 kB 🟢 -3.63 kB
assets/releaseStore-KNTwbnX9.js (new) 7.96 kB 🔴 +7.96 kB 🔴 +2.22 kB 🔴 +1.95 kB
assets/releaseStore-qpRoV7cQ.js (removed) 7.96 kB 🟢 -7.96 kB 🟢 -2.22 kB 🟢 -1.95 kB
assets/keybindingService-BHFI-mbx.js (removed) 7.16 kB 🟢 -7.16 kB 🟢 -1.72 kB 🟢 -1.48 kB
assets/keybindingService-DsweANKC.js (new) 7.16 kB 🔴 +7.16 kB 🔴 +1.72 kB 🔴 +1.48 kB
assets/bootstrapStore-BjK1mpph.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +874 B 🔴 +792 B
assets/bootstrapStore-kjHkQmWM.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -872 B 🟢 -799 B
assets/userStore-CwcZrDPW.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -719 B 🟢 -669 B
assets/userStore-CywJmziF.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +720 B 🔴 +632 B
assets/audioService-tG0y3dis.js (new) 1.73 kB 🔴 +1.73 kB 🔴 +847 B 🔴 +728 B
assets/audioService-yN4COGVz.js (removed) 1.73 kB 🟢 -1.73 kB 🟢 -849 B 🟢 -724 B
assets/releaseStore-CASxbr-a.js (new) 803 B 🔴 +803 B 🔴 +404 B 🔴 +355 B
assets/releaseStore-CkoRMS9w.js (removed) 803 B 🟢 -803 B 🟢 -402 B 🟢 -356 B
assets/settingStore-DCNBme4W.js (new) 787 B 🔴 +787 B 🔴 +405 B 🔴 +356 B
assets/settingStore-yFmE8942.js (removed) 787 B 🟢 -787 B 🟢 -403 B 🟢 -357 B
assets/workflowDraftStore-CCwoEmVQ.js (removed) 779 B 🟢 -779 B 🟢 -396 B 🟢 -348 B
assets/workflowDraftStore-DDz45jFp.js (new) 779 B 🔴 +779 B 🔴 +396 B 🔴 +348 B
assets/dialogService--_3CZN93.js (removed) 768 B 🟢 -768 B 🟢 -387 B 🟢 -342 B
assets/dialogService-BQl3vyCL.js (new) 768 B 🔴 +768 B 🔴 +388 B 🔴 +341 B
assets/assetsStore-9fo9IgGF.js (new) 765 B 🔴 +765 B 🔴 +391 B 🔴 +341 B
assets/assetsStore-CYOLMdoC.js (removed) 765 B 🟢 -765 B 🟢 -391 B 🟢 -343 B
assets/serverConfigStore-i8JbF5tX.js 2.32 kB 2.32 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 15 added / 15 removed

Utilities & Hooks — 61.5 kB (baseline 61.5 kB) • ⚪ 0 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useLoad3d-ClGqzIeT.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.63 kB 🔴 +3.21 kB
assets/useLoad3d-DIEDJPL3.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.63 kB 🟢 -3.21 kB
assets/useLoad3dViewer-BwpKJ2LV.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.19 kB 🔴 +2.83 kB
assets/useLoad3dViewer-CC7Pzo6E.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.19 kB 🟢 -2.83 kB
assets/useFeatureFlags-BNdPqWK3.js (new) 4.86 kB 🔴 +4.86 kB 🔴 +1.37 kB 🔴 +1.17 kB
assets/useFeatureFlags-Cbi7hMWg.js (removed) 4.86 kB 🟢 -4.86 kB 🟢 -1.37 kB 🟢 -1.17 kB
assets/useWorkspaceUI-DQSMVH2W.js (new) 3 kB 🔴 +3 kB 🔴 +822 B 🔴 +702 B
assets/useWorkspaceUI-MgufD0iK.js (removed) 3 kB 🟢 -3 kB 🟢 -820 B 🟢 -704 B
assets/subscriptionCheckoutUtil-bTZ0V8LU.js (new) 2.53 kB 🔴 +2.53 kB 🔴 +1.06 kB 🔴 +960 B
assets/subscriptionCheckoutUtil-U1gdscFN.js (removed) 2.53 kB 🟢 -2.53 kB 🟢 -1.05 kB 🟢 -928 B
assets/useErrorHandling-3nAmvcYV.js (new) 1.5 kB 🔴 +1.5 kB 🔴 +630 B 🔴 +535 B
assets/useErrorHandling-D-H2k4zI.js (removed) 1.5 kB 🟢 -1.5 kB 🟢 -630 B 🟢 -571 B
assets/useLoad3d-BH27IoTv.js (removed) 902 B 🟢 -902 B 🟢 -440 B 🟢 -392 B
assets/useLoad3d-D9fOezCE.js (new) 902 B 🔴 +902 B 🔴 +441 B 🔴 +392 B
assets/useLoad3dViewer-C917Fp3s.js (new) 881 B 🔴 +881 B 🔴 +426 B 🔴 +381 B
assets/useLoad3dViewer-DGzQq5o1.js (removed) 881 B 🟢 -881 B 🟢 -426 B 🟢 -381 B
assets/audioUtils-BV57BX4j.js (new) 858 B 🔴 +858 B 🔴 +502 B 🔴 +425 B
assets/audioUtils-SwJSumH1.js (removed) 858 B 🟢 -858 B 🟢 -497 B 🟢 -404 B
assets/useCurrentUser-B67x1TOH.js (removed) 765 B 🟢 -765 B 🟢 -390 B 🟢 -341 B
assets/useCurrentUser-wM7XykAp.js (new) 765 B 🔴 +765 B 🔴 +391 B 🔴 +339 B
assets/useWorkspaceSwitch-FGGPmKXP.js (removed) 688 B 🟢 -688 B 🟢 -351 B 🟢 -296 B
assets/useWorkspaceSwitch-qgvjOy_P.js (new) 688 B 🔴 +688 B 🔴 +351 B 🔴 +296 B
assets/_plugin-vue_export-helper-C4xK3rHS.js 315 B 315 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/assetMetadataUtils-BOUbjmqE.js 2.61 kB 2.61 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/colorUtil-Dzwmca-S.js 8.72 kB 8.72 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/envUtil-Clzmwvt4.js 466 B 466 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/markdownRendererUtil-CzcsgKaH.js 1.56 kB 1.56 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SkeletonUtils-u0px2nND.js 133 B 133 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useCopyToClipboard-DsCYbdFi.js 944 B 944 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useExternalLink-Cg9rV8n1.js 1.66 kB 1.66 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 11 added / 11 removed

Vendor & Third-Party — 8.9 MB (baseline 8.9 MB) • ⚪ 0 B

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-axios-Cp6hch1I.js 70.7 kB 70.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-chart-hHaxyObP.js 399 kB 399 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-firebase-BvMr43CG.js 836 kB 836 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-i18n-DNX73mqE.js 133 kB 133 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-markdown-D5S6AC80.js 103 kB 103 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-other-B9hcW0fn.js 1.54 MB 1.54 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-primevue-BgyU4sWT.js 1.72 MB 1.72 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-reka-ui-DlFrLeZ5.js 428 kB 428 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-sentry-SQwstEKc.js 182 kB 182 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-three-B-Fxd75p.js 1.8 MB 1.8 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-tiptap-C9679tdI.js 634 kB 634 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vue-core-DtiQ1dr9.js 311 kB 311 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vueuse-7Q5M-X2w.js 125 kB 125 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-BmNn97E7.js 374 kB 374 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-yjs-CP_4YO8u.js 143 kB 143 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-zod-DcCUUPIi.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
Other — 8.16 MB (baseline 8.16 MB) • 🔴 +103 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-BRSWKJPk.js (removed) 73.8 kB 🟢 -73.8 kB 🟢 -19.1 kB 🟢 -16.3 kB
assets/core-CA06HNYc.js (new) 73.8 kB 🔴 +73.8 kB 🔴 +19.1 kB 🔴 +16.3 kB
assets/groupNode-7B_siKR5.js (new) 71.8 kB 🔴 +71.8 kB 🔴 +17.7 kB 🔴 +15.6 kB
assets/groupNode-DBoZDqXa.js (removed) 71.8 kB 🟢 -71.8 kB 🟢 -17.7 kB 🟢 -15.5 kB
assets/WidgetSelect-w6wGS2Wo.js (new) 58.4 kB 🔴 +58.4 kB 🔴 +12.5 kB 🔴 +10.8 kB
assets/WidgetSelect-DnNBOfDG.js (removed) 58.3 kB 🟢 -58.3 kB 🟢 -12.5 kB 🟢 -10.8 kB
assets/SubscriptionRequiredDialogContentWorkspace-BddXFypW.js (removed) 46.2 kB 🟢 -46.2 kB 🟢 -8.64 kB 🟢 -7.5 kB
assets/SubscriptionRequiredDialogContentWorkspace-RZcDVs1d.js (new) 46.2 kB 🔴 +46.2 kB 🔴 +8.65 kB 🔴 +7.5 kB
assets/WidgetPainter-BTxHmNml.js (removed) 32.9 kB 🟢 -32.9 kB 🟢 -7.97 kB 🟢 -7.06 kB
assets/WidgetPainter-De1gc85M.js (new) 32.9 kB 🔴 +32.9 kB 🔴 +7.97 kB 🔴 +7.08 kB
assets/Load3DControls-ByGI65RV.js (removed) 30.9 kB 🟢 -30.9 kB 🟢 -5.34 kB 🟢 -4.65 kB
assets/Load3DControls-WjqvAY-C.js (new) 30.9 kB 🔴 +30.9 kB 🔴 +5.34 kB 🔴 +4.66 kB
assets/WorkspacePanelContent-BdIdtmok.js (new) 29.3 kB 🔴 +29.3 kB 🔴 +6.16 kB 🔴 +5.41 kB
assets/WorkspacePanelContent-DBWx9KCO.js (removed) 29.3 kB 🟢 -29.3 kB 🟢 -6.16 kB 🟢 -5.42 kB
assets/SubscriptionRequiredDialogContent-CyZEu4c7.js (new) 25.6 kB 🔴 +25.6 kB 🔴 +6.56 kB 🔴 +5.78 kB
assets/SubscriptionRequiredDialogContent-DZEjb4Ko.js (removed) 25.6 kB 🟢 -25.6 kB 🟢 -6.55 kB 🟢 -5.77 kB
assets/Load3dViewerContent-mHM1wxA2.js (new) 23 kB 🔴 +23 kB 🔴 +5.19 kB 🔴 +4.5 kB
assets/Load3dViewerContent-Xtfhjet5.js (removed) 23 kB 🟢 -23 kB 🟢 -5.18 kB 🟢 -4.5 kB
assets/WidgetImageCrop-B9OWAXtr.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -5.51 kB 🟢 -4.86 kB
assets/WidgetImageCrop-BSunn2LQ.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +5.52 kB 🔴 +4.86 kB
assets/SubscriptionPanelContentWorkspace-DqeF8SfQ.js (removed) 22 kB 🟢 -22 kB 🟢 -5.11 kB 🟢 -4.49 kB
assets/SubscriptionPanelContentWorkspace-zalOCMjR.js (new) 22 kB 🔴 +22 kB 🔴 +5.11 kB 🔴 +4.49 kB
assets/CurrentUserPopoverWorkspace-DGsnd9m1.js (new) 20.5 kB 🔴 +20.5 kB 🔴 +4.94 kB 🔴 +4.42 kB
assets/CurrentUserPopoverWorkspace-fNfccLQI.js (removed) 20.5 kB 🟢 -20.5 kB 🟢 -4.94 kB 🟢 -4.41 kB
assets/SignInContent-D2FBTMl5.js (new) 18.9 kB 🔴 +18.9 kB 🔴 +4.77 kB 🔴 +4.18 kB
assets/SignInContent-EpvHky8-.js (removed) 18.9 kB 🟢 -18.9 kB 🟢 -4.76 kB 🟢 -4.17 kB
assets/WidgetInputNumber-B1mfNCr2.js (removed) 18.1 kB 🟢 -18.1 kB 🟢 -4.64 kB 🟢 -4.13 kB
assets/WidgetInputNumber-NtIszfJl.js (new) 18.1 kB 🔴 +18.1 kB 🔴 +4.64 kB 🔴 +4.12 kB
assets/WidgetRecordAudio-CzlPlJkE.js (new) 17.4 kB 🔴 +17.4 kB 🔴 +5.01 kB 🔴 +4.48 kB
assets/WidgetRecordAudio-vYRsnRqB.js (removed) 17.4 kB 🟢 -17.4 kB 🟢 -5.01 kB 🟢 -4.48 kB
assets/Load3D-BJBBk4Gd.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -4.03 kB 🟢 -3.5 kB
assets/Load3D-E4Q9bV2T.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +4.03 kB 🔴 +3.51 kB
assets/load3d-Ljmemvqc.js (removed) 14.8 kB 🟢 -14.8 kB 🟢 -4.2 kB 🟢 -3.64 kB
assets/load3d-wuSgL2QO.js (new) 14.8 kB 🔴 +14.8 kB 🔴 +4.21 kB 🔴 +3.65 kB
assets/AudioPreviewPlayer-BkQsMVT3.js (new) 11 kB 🔴 +11 kB 🔴 +3.25 kB 🔴 +2.92 kB
assets/AudioPreviewPlayer-DWoBYbrQ.js (removed) 11 kB 🟢 -11 kB 🟢 -3.25 kB 🟢 -2.91 kB
assets/nodeTemplates-C1LqcWSA.js (new) 9.34 kB 🔴 +9.34 kB 🔴 +3.27 kB 🔴 +2.88 kB
assets/nodeTemplates-YkQIRwDK.js (removed) 9.34 kB 🟢 -9.34 kB 🟢 -3.27 kB 🟢 -2.87 kB
assets/InviteMemberDialogContent-CsHIim2E.js (new) 7.37 kB 🔴 +7.37 kB 🔴 +2.3 kB 🔴 +2.01 kB
assets/InviteMemberDialogContent-CZkaGTih.js (removed) 7.37 kB 🟢 -7.37 kB 🟢 -2.3 kB 🟢 -2.01 kB
assets/Load3DConfiguration-B-XryYWP.js (removed) 6.27 kB 🟢 -6.27 kB 🟢 -1.91 kB 🟢 -1.68 kB
assets/Load3DConfiguration-DZRKhT5b.js (new) 6.27 kB 🔴 +6.27 kB 🔴 +1.92 kB 🔴 +1.68 kB
assets/onboardingCloudRoutes-BP4dPamC.js (new) 6.15 kB 🔴 +6.15 kB 🔴 +1.91 kB 🔴 +1.65 kB
assets/onboardingCloudRoutes-DzB30bWw.js (removed) 6.15 kB 🟢 -6.15 kB 🟢 -1.91 kB 🟢 -1.65 kB
assets/CreateWorkspaceDialogContent-8FCNCZPh.js (removed) 5.54 kB 🟢 -5.54 kB 🟢 -2 kB 🟢 -1.74 kB
assets/CreateWorkspaceDialogContent-BslHy3Js.js (new) 5.54 kB 🔴 +5.54 kB 🔴 +2 kB 🔴 +1.75 kB
assets/FreeTierDialogContent-D1-3nXUj.js (new) 5.42 kB 🔴 +5.42 kB 🔴 +1.91 kB 🔴 +1.68 kB
assets/FreeTierDialogContent-uNIBNJSx.js (removed) 5.42 kB 🟢 -5.42 kB 🟢 -1.91 kB 🟢 -1.68 kB
assets/EditWorkspaceDialogContent-1-S7GBjh.js (new) 5.35 kB 🔴 +5.35 kB 🔴 +1.97 kB 🔴 +1.72 kB
assets/EditWorkspaceDialogContent-Df_KocTD.js (removed) 5.35 kB 🟢 -5.35 kB 🟢 -1.96 kB 🟢 -1.72 kB
assets/Preview3d-Bxvd8nfX.js (removed) 4.96 kB 🟢 -4.96 kB 🟢 -1.63 kB 🟢 -1.43 kB
assets/Preview3d-Cz3gboie.js (new) 4.96 kB 🔴 +4.96 kB 🔴 +1.63 kB 🔴 +1.43 kB
assets/ValueControlPopover-BCgTRiDd.js (removed) 4.93 kB 🟢 -4.93 kB 🟢 -1.76 kB 🟢 -1.58 kB
assets/ValueControlPopover-ChtGxQPb.js (new) 4.93 kB 🔴 +4.93 kB 🔴 +1.77 kB 🔴 +1.59 kB
assets/CancelSubscriptionDialogContent-DRwq-usk.js (new) 4.81 kB 🔴 +4.81 kB 🔴 +1.8 kB 🔴 +1.58 kB
assets/CancelSubscriptionDialogContent-hzmp3z_M.js (removed) 4.81 kB 🟢 -4.81 kB 🟢 -1.8 kB 🟢 -1.58 kB
assets/DeleteWorkspaceDialogContent-DDyAKIMc.js (removed) 4.25 kB 🟢 -4.25 kB 🟢 -1.64 kB 🟢 -1.43 kB
assets/DeleteWorkspaceDialogContent-kAROSPQR.js (new) 4.25 kB 🔴 +4.25 kB 🔴 +1.64 kB 🔴 +1.43 kB
assets/WidgetWithControl-C1d-kJx0.js (new) 4.24 kB 🔴 +4.24 kB 🔴 +1.85 kB 🔴 +1.65 kB
assets/WidgetWithControl-DNHIzXd0.js (removed) 4.24 kB 🟢 -4.24 kB 🟢 -1.85 kB 🟢 -1.66 kB
assets/LeaveWorkspaceDialogContent-BFKCdYz2.js (removed) 4.08 kB 🟢 -4.08 kB 🟢 -1.58 kB 🟢 -1.38 kB
assets/LeaveWorkspaceDialogContent-iOIu-TjN.js (new) 4.08 kB 🔴 +4.08 kB 🔴 +1.59 kB 🔴 +1.39 kB
assets/RemoveMemberDialogContent-BvNSkUPE.js (new) 4.06 kB 🔴 +4.06 kB 🔴 +1.54 kB 🔴 +1.35 kB
assets/RemoveMemberDialogContent-CbTPZvec.js (removed) 4.06 kB 🟢 -4.06 kB 🟢 -1.54 kB 🟢 -1.34 kB
assets/RevokeInviteDialogContent-DAZb2HGJ.js (removed) 3.97 kB 🟢 -3.97 kB 🟢 -1.55 kB 🟢 -1.36 kB
assets/RevokeInviteDialogContent-Nsx5Tyfu.js (new) 3.97 kB 🔴 +3.97 kB 🔴 +1.55 kB 🔴 +1.36 kB
assets/InviteMemberUpsellDialogContent-BUFw4eDf.js (new) 3.86 kB 🔴 +3.86 kB 🔴 +1.42 kB 🔴 +1.25 kB
assets/InviteMemberUpsellDialogContent-Sgi-_V83.js (removed) 3.86 kB 🟢 -3.86 kB 🟢 -1.42 kB 🟢 -1.25 kB
assets/tierBenefits-BbElxgM6.js (removed) 3.66 kB 🟢 -3.66 kB 🟢 -1.3 kB 🟢 -1.17 kB
assets/tierBenefits-BE6Vuzjs.js (new) 3.66 kB 🔴 +3.66 kB 🔴 +1.31 kB 🔴 +1.14 kB
assets/saveMesh-C5Tm1lO9.js (removed) 3.42 kB 🟢 -3.42 kB 🟢 -1.47 kB 🟢 -1.31 kB
assets/saveMesh-C6VdWtmE.js (new) 3.42 kB 🔴 +3.42 kB 🔴 +1.47 kB 🔴 +1.31 kB
assets/cloudSessionCookie-CQLRATvl.js (new) 3.14 kB 🔴 +3.14 kB 🔴 +1.11 kB 🔴 +999 B
assets/cloudSessionCookie-DzJuG6dJ.js (removed) 3.14 kB 🟢 -3.14 kB 🟢 -1.1 kB 🟢 -1.01 kB
assets/GlobalToast-B-4fBQWF.js (removed) 2.91 kB 🟢 -2.91 kB 🟢 -1.21 kB 🟢 -1.03 kB
assets/GlobalToast-CWdcx9TE.js (new) 2.91 kB 🔴 +2.91 kB 🔴 +1.21 kB 🔴 +1.06 kB
assets/SubscribeToRun-D4xU6Pdq.js (removed) 2.06 kB 🟢 -2.06 kB 🟢 -948 B 🟢 -838 B
assets/SubscribeToRun-QxGKuOzw.js (new) 2.06 kB 🔴 +2.06 kB 🔴 +948 B 🔴 +844 B
assets/CloudRunButtonWrapper-B2wpHLEV.js (removed) 1.72 kB 🟢 -1.72 kB 🟢 -802 B 🟢 -737 B
assets/CloudRunButtonWrapper-CFBFpiNS.js (new) 1.72 kB 🔴 +1.72 kB 🔴 +805 B 🔴 +735 B
assets/cloudBadges-CnyqPI09.js (removed) 1.54 kB 🟢 -1.54 kB 🟢 -791 B 🟢 -691 B
assets/cloudBadges-eDACnjl9.js (new) 1.54 kB 🔴 +1.54 kB 🔴 +794 B 🔴 +695 B
assets/previousFullPath-Bh9k8QHQ.js (new) 1.39 kB 🔴 +1.39 kB 🔴 +651 B 🔴 +578 B
assets/previousFullPath-CFPinhUH.js (removed) 1.39 kB 🟢 -1.39 kB 🟢 -647 B 🟢 -578 B
assets/cloudSubscription-D5UHLayy.js (removed) 1.37 kB 🟢 -1.37 kB 🟢 -672 B 🟢 -583 B
assets/cloudSubscription-DE_0nmI9.js (new) 1.37 kB 🔴 +1.37 kB 🔴 +676 B 🔴 +582 B
assets/Load3D-BTwEl4D6.js (new) 1.11 kB 🔴 +1.11 kB 🔴 +520 B 🔴 +458 B
assets/Load3D-DYkZd22w.js (removed) 1.11 kB 🟢 -1.11 kB 🟢 -515 B 🟢 -459 B
assets/nightlyBadges-B8qg0_xE.js (new) 1.04 kB 🔴 +1.04 kB 🔴 +550 B 🔴 +489 B
assets/nightlyBadges-pEUZuddh.js (removed) 1.04 kB 🟢 -1.04 kB 🟢 -547 B 🟢 -485 B
assets/Load3dViewerContent-BfObGVCg.js (removed) 1.04 kB 🟢 -1.04 kB 🟢 -484 B 🟢 -429 B
assets/Load3dViewerContent-CbUtYPBi.js (new) 1.04 kB 🔴 +1.04 kB 🔴 +488 B 🔴 +432 B
assets/SubscriptionPanelContentWorkspace-CNra_yZP.js (new) 963 B 🔴 +963 B 🔴 +459 B 🔴 +398 B
assets/SubscriptionPanelContentWorkspace-u6S852zB.js (removed) 963 B 🟢 -963 B 🟢 -454 B 🟢 -395 B
assets/WidgetLegacy-B_W_EUlK.js (removed) 787 B 🟢 -787 B 🟢 -401 B 🟢 -347 B
assets/WidgetLegacy-BcvePAEo.js (new) 787 B 🔴 +787 B 🔴 +405 B 🔴 +348 B
assets/changeTracker-Bq0JSLHM.js (new) 763 B 🔴 +763 B 🔴 +392 B 🔴 +337 B
assets/changeTracker-lwoIbHfG.js (removed) 763 B 🟢 -763 B 🟢 -390 B 🟢 -341 B
assets/graphHasMissingNodes-C171L4it.js (new) 761 B 🔴 +761 B 🔴 +373 B 🔴 +320 B
assets/graphHasMissingNodes-CmZ6ngk-.js (removed) 761 B 🟢 -761 B 🟢 -372 B 🟢 -329 B
assets/AnimationControls-BJbsMbSG.js 4.61 kB 4.61 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ApiNodesSignInContent-zbTE6PEB.js 2.69 kB 2.69 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/auto-CREurdFv.js 1.7 kB 1.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/BaseViewTemplate-CBLfDo11.js 1.78 kB 1.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/comfy-logo-single-Da55e1mo.js 198 B 198 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ComfyOrgHeader-D7-DPX58.js 910 B 910 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-B263T0mS.js 17.3 kB 17.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Bghyd0Lh.js 16.5 kB 16.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BJ42IZdd.js 16.5 kB 16.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BT_6gpI-.js 15.4 kB 15.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CgrNaCrU.js 16.4 kB 16.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-ChUjv_HG.js 16.3 kB 16.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-dTh2lNoX.js 19.2 kB 19.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DuPpT3ev.js 17.9 kB 17.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-nDiUmYC8.js 17 kB 17 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-oUBtGrlW.js 15.6 kB 15.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-s62bKqb7.js 17.9 kB 17.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/constants-iJ2Gpnh7.js 579 B 579 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-B9oMOOwH.js 552 kB 552 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-nRwv5VM4.js 199 B 199 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Loader-DqEwymFo.js 1.14 kB 1.14 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-7ldeCXVv.js 142 kB 142 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BCnGXNlx.js 164 kB 164 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BHYXqIHA.js 161 kB 161 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Bq4O9q7m.js 167 kB 167 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Bzu4ZNWZ.js 194 kB 194 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CtLCVBWe.js 186 kB 186 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Dh_Qurzi.js 162 kB 162 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Dj-BTR_-.js 227 kB 227 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DozMGpwc.js 144 kB 144 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DzL-b3qQ.js 203 kB 203 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-pEXTnq0E.js 170 kB 170 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Media3DTop-CN16pADK.js 1.83 kB 1.83 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaAudioTop-uat1AWK0.js 1.43 kB 1.43 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaImageTop-BuvPJhXs.js 1.87 kB 1.87 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaOtherTop-Lejn-MBp.js 1.02 kB 1.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaTextTop-BSKfmx1L.js 1.01 kB 1.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaVideoTop-WhKRHkLf.js 2.78 kB 2.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/NightlySurveyController-COAvTr7F.js 8.92 kB 8.92 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-7OBck-nz.js 458 kB 458 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-B5mgK59H.js 411 kB 411 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-bxbYjSw0.js 500 kB 500 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-ByZhGXW8.js 459 kB 459 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BZRR7pTo.js 403 kB 403 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Cay4VJYh.js 398 kB 398 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CFFGM1nr.js 423 kB 423 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CqYex9nB.js 407 kB 407 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CsqA39b3.js 406 kB 406 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-D_jQStz7.js 370 kB 370 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-sn26hl8g.js 373 kB 373 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Popover-4Y59q1Nj.js 3.65 kB 3.65 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/rolldown-runtime-DLICfi3-.js 1.97 kB 1.97 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SelectValue-B83isiBl.js 9.34 kB 9.34 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/signInSchema-BQjGcE3h.js 1.53 kB 1.53 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Slider-Bjn2amW6.js 3.52 kB 3.52 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/src-C0aGDOQV.js 251 B 251 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionBenefits-B6afyggK.js 2.01 kB 2.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/telemetry-zZf2dHJ2.js 226 B 226 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Textarea-fsa38wWk.js 1.37 kB 1.37 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/types-DT3N7am7.js 204 B 204 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/VideoPlayOverlay-ByWFGcRB.js 1.35 kB 1.35 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widget-DIRwAHBY.js 3.01 kB 3.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-aWm_WdMu.js 3.19 kB 3.19 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetBoundingBox-CZQndtfK.js 283 B 283 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetChart-CrfZL3eK.js 2.21 kB 2.21 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetColorPicker-BZStVx1d.js 15.4 kB 15.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetCurve-DjdkIHnc.js 9.36 kB 9.36 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetGalleria-BUIHLxjo.js 3.6 kB 3.6 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetImageCompare-DxaZ1_SF.js 7.35 kB 7.35 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetInputText-BZ5Jb1fX.js 2.89 kB 2.89 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetLayoutField-CrHq2zp5.js 2.15 kB 2.15 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetMarkdown-GwfUTE2m.js 2.93 kB 2.93 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-C_wtEFyS.js 1.11 kB 1.11 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetTextarea-DOyWtA5O.js 4.26 kB 4.26 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetToggleSwitch-BOn4oTIJ.js 3.65 kB 3.65 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetTypes-DzBxiY8I.js 393 B 393 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 50 added / 50 removed

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts (1)

32-33: Consider optional chaining for defensive access.

If graph.extra is ever undefined, this line would throw a TypeError. While LGraph typically always initializes extra, optional chaining would be more defensive.

🛡️ Optional defensive fix
-  const savedRenderer = graph.extra.workflowRendererVersion ?? renderer
+  const savedRenderer = graph.extra?.workflowRendererVersion ?? renderer
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts` around
lines 32 - 33, The access to graph.extra is not defensive; change the retrieval
of savedRenderer to use optional chaining so it reads the workflow renderer
safely (replace graph.extra.workflowRendererVersion with
graph.extra?.workflowRendererVersion) — i.e., update the expression that sets
savedRenderer in ensureCorrectLayoutScale to use
graph.extra?.workflowRendererVersion ?? renderer and keep the existing if
(!savedRenderer) return guard.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.test.ts`:
- Around line 172-174: The test comment is misleading: with savedExtra = {} and
renderer = undefined the savedRenderer remains undefined so
ensureCorrectLayoutScale runs as a no-op and distances remain equal; update the
comment above the assertions (near the distances expectations) to state that
this test verifies the "no-op when renderer unknown / savedRenderer undefined"
behavior (or alternatively modify the test setup by providing a non-undefined
renderer/savedExtra to actually test the "first load scales up, subsequent loads
do not compound" scenario), referencing savedExtra, renderer, savedRenderer, and
the distances assertions so maintainers can find and understand the change.

---

Nitpick comments:
In `@src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts`:
- Around line 32-33: The access to graph.extra is not defensive; change the
retrieval of savedRenderer to use optional chaining so it reads the workflow
renderer safely (replace graph.extra.workflowRendererVersion with
graph.extra?.workflowRendererVersion) — i.e., update the expression that sets
savedRenderer in ensureCorrectLayoutScale to use
graph.extra?.workflowRendererVersion ?? renderer and keep the existing if
(!savedRenderer) return guard.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 40d153ee-9911-44e2-9bc2-331b9797084c

📥 Commits

Reviewing files that changed from the base of the PR and between 2ccfb82 and dbd76a0.

📒 Files selected for processing (2)
  • src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.test.ts
  • src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts

@github-actions
Copy link

github-actions bot commented Mar 9, 2026

⚡ Performance Report

ℹ️ Collecting baseline variance data (0/5 runs). Significance will appear after 2 main branch runs.

Metric Baseline PR Δ
canvas-idle: style recalcs 11 11 -3%
canvas-idle: layouts 0 0 +0%
canvas-idle: task duration 354ms 411ms +16%
canvas-mouse-sweep: style recalcs 79 79 +0%
canvas-mouse-sweep: layouts 12 12 +0%
canvas-mouse-sweep: task duration 855ms 869ms +2%
dom-widget-clipping: style recalcs 13 14 +8%
dom-widget-clipping: layouts 0 0
dom-widget-clipping: task duration 335ms 365ms +9%
subgraph-dom-widget-clipping: style recalcs 49 49 +0%
subgraph-dom-widget-clipping: layouts 0 0
subgraph-dom-widget-clipping: task duration 403ms 454ms +13%
subgraph-idle: style recalcs 13 11 -13%
subgraph-idle: layouts 1 0 -100%
subgraph-idle: task duration 353ms 404ms +14%
subgraph-mouse-sweep: style recalcs 76 77 +1%
subgraph-mouse-sweep: layouts 16 16 +0%
subgraph-mouse-sweep: task duration 710ms 747ms +5%
Raw data
{
  "timestamp": "2026-03-13T08:06:51.774Z",
  "gitSha": "84c9a0936651d39af2c2323494288f464f553056",
  "branch": "drjkl/dark-energy",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2031.4730000000054,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 10.341999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 415.41299999999995,
      "heapDeltaBytes": 798080
    },
    {
      "name": "canvas-idle",
      "durationMs": 2038.5099999999738,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 11.091999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 406.87600000000003,
      "heapDeltaBytes": 780036
    },
    {
      "name": "canvas-idle",
      "durationMs": 2035.3390000000218,
      "styleRecalcs": 10,
      "styleRecalcDurationMs": 11.005,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 410.22,
      "heapDeltaBytes": 864108
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1954.430000000002,
      "styleRecalcs": 80,
      "styleRecalcDurationMs": 44.736000000000004,
      "layouts": 12,
      "layoutDurationMs": 3.908,
      "taskDurationMs": 868.593,
      "heapDeltaBytes": -5248424
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1995.4099999999926,
      "styleRecalcs": 82,
      "styleRecalcDurationMs": 47.315999999999995,
      "layouts": 12,
      "layoutDurationMs": 4.029000000000001,
      "taskDurationMs": 941.936,
      "heapDeltaBytes": 1476692
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1828.6719999999832,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 41.182,
      "layouts": 12,
      "layoutDurationMs": 3.801,
      "taskDurationMs": 795.208,
      "heapDeltaBytes": 1550236
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 560.483999999974,
      "styleRecalcs": 14,
      "styleRecalcDurationMs": 10.382000000000001,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 349.87,
      "heapDeltaBytes": 13063584
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 608.2260000000019,
      "styleRecalcs": 13,
      "styleRecalcDurationMs": 9.948,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 378.28999999999996,
      "heapDeltaBytes": 12634420
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 582.1530000000052,
      "styleRecalcs": 16,
      "styleRecalcDurationMs": 14.982000000000003,
      "layouts": 1,
      "layoutDurationMs": 0.2980000000000001,
      "taskDurationMs": 368.093,
      "heapDeltaBytes": 13476156
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 595.3460000000064,
      "styleRecalcs": 49,
      "styleRecalcDurationMs": 14.658,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 427.477,
      "heapDeltaBytes": -3819676
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 681.5839999999866,
      "styleRecalcs": 48,
      "styleRecalcDurationMs": 15.177999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 455.20000000000005,
      "heapDeltaBytes": -3806176
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 705.8150000000296,
      "styleRecalcs": 51,
      "styleRecalcDurationMs": 19.963,
      "layouts": 1,
      "layoutDurationMs": 0.22500000000000006,
      "taskDurationMs": 480.558,
      "heapDeltaBytes": 15989916
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1997.4500000000148,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 10.342,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 348.78,
      "heapDeltaBytes": 1283224
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1995.1790000000074,
      "styleRecalcs": 11,
      "styleRecalcDurationMs": 11.468999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 413.62100000000004,
      "heapDeltaBytes": 600008
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2008.5860000000366,
      "styleRecalcs": 12,
      "styleRecalcDurationMs": 12.270999999999999,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 448.402,
      "heapDeltaBytes": 534992
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1695.934999999963,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 36.46,
      "layouts": 16,
      "layoutDurationMs": 4.228,
      "taskDurationMs": 680.3019999999999,
      "heapDeltaBytes": -1113992
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1722.3199999999679,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 42.086000000000006,
      "layouts": 16,
      "layoutDurationMs": 4.682,
      "taskDurationMs": 784.524,
      "heapDeltaBytes": -1030808
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1682.2760000000017,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 38.12199999999999,
      "layouts": 16,
      "layoutDurationMs": 4.612,
      "taskDurationMs": 777.146,
      "heapDeltaBytes": -757448
    }
  ]
}

@DrJKL DrJKL marked this pull request as draft March 9, 2026 23:50
- Replace coordinateSpaceVersion + workflowRendererVersion deletion with
  a single 'Vue-corrected' RendererType value
- Make graph parameter required; remove comfyApp fallback
- Reuse Point/Bounds types from layout/types instead of local interfaces
- Remove no-op ensureCorrectLayoutScale calls from useVueNodeLifecycle
- Update callers in app.ts to always pass graph explicitly
- Add E2E test for renderer toggle position stability

Amp-Thread-ID: https://ampcode.com/threads/T-019cd4e5-bc6d-72ee-8677-554212a98419
Co-authored-by: Amp <amp@ampcode.com>
@github-actions
Copy link

🎨 Storybook: loading Building...

Remove setTimeout wrapper from notifyChange in finalizeOperation so useLayoutSync pushes positions to LiteGraph within the same RAF frame as the drag update. This fixes links not following Vue nodes during drag.

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019cd504-cbcc-723e-b2c4-06eb6e488101
@DrJKL DrJKL force-pushed the drjkl/dark-energy branch from 595a30b to 9e72a6e Compare March 10, 2026 00:02
@DrJKL DrJKL changed the title fix: make ensureCorrectLayoutScale idempotent to prevent node layout drift fix: simplify ensureCorrectLayoutScale and fix link sync during Vue node drag Mar 10, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
src/scripts/app.ts (1)

1316-1321: ⚠️ Potential issue | 🟠 Major

Parameter precedence in ensureCorrectLayoutScale() creates re-normalization risk.

The helper uses rendererVersion ?? graph.extra.workflowRendererVersion, prioritizing the parameter over the graph's current metadata. If the function is called on an already-normalized graph with a stale 'Vue' parameter, it will skip the 'Vue-corrected' marker and attempt re-normalization—applying the coordinate transform twice, which inverts the correction.

For the call at line 1321, the parameter is read immediately before use, so the risk is contained within this operation. However, the design lacks idempotency protection. Consider restructuring to:

  • Prefer graph.extra.workflowRendererVersion and treat the parameter as a true fallback only when graph metadata is missing
  • Add a guard: return early if graph.extra.workflowRendererVersion === 'Vue-corrected', regardless of parameter value
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/scripts/app.ts` around lines 1316 - 1321, The helper
ensureCorrectLayoutScale currently prefers the rendererVersion parameter over
graph.extra.workflowRendererVersion, which can cause double-renormalization;
update ensureCorrectLayoutScale so it first reads
graph.extra.workflowRendererVersion (use the parameter rendererVersion only as a
fallback when the graph metadata is missing), and add an early guard that
returns immediately if graph.extra.workflowRendererVersion === 'Vue-corrected'
to make the function idempotent; adjust callers like
ensureCorrectLayoutScale(originalMainGraphRenderer, this.rootGraph) only if they
rely on the old precedence.
🧹 Nitpick comments (3)
browser_tests/tests/rendererToggleStability.spec.ts (1)

35-41: Load a pinned workflow fixture before asserting positions.

This snapshots whatever graph the fixture happens to boot with, so the test's coverage and stability will drift with unrelated startup/default-workflow changes. Load a known JSON from assets/ first, then run the toggle assertions against that fixed graph.

As per coding guidelines, use premade JSON workflows in the assets/ directory to load desired graph state in E2E tests.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@browser_tests/tests/rendererToggleStability.spec.ts` around lines 35 - 41,
The test "node positions do not drift when toggling between Vue and LiteGraph
renderers" currently snapshots whatever graph is present at startup; before
calling getAllNodePositions(comfyPage) load a deterministic workflow JSON from
the assets/ directory (e.g., a premade fixture like
assets/<desired-workflow>.json) into the app using the test page helper (via
comfyPage import/upload or the app's load workflow API), wait for the workflow
to finish loading (nodes rendered), then proceed with the TOGGLE_COUNT loop and
assertions; update the test body around getAllNodePositions to perform this
deterministic load so positions are asserted against a known graph.
src/renderer/core/layout/transform/graphRenderTransform.test.ts (1)

89-99: Consider using satisfies for mock type validation.

The as never casts work but lose type safety. Per repository learnings, prefer satisfies for mock objects to maintain shape validation while allowing partial implementations.

♻️ Optional improvement using satisfies pattern
-      const anchor1 = getGraphRenderAnchor(mockGraph as never)
+      const anchor1 = getGraphRenderAnchor(mockGraph as unknown as LGraph)
       // Mutate positions — anchor should stay frozen
       mockGraph.nodes[0].pos = [500, 600]
-      const anchor2 = getGraphRenderAnchor(mockGraph as never)
+      const anchor2 = getGraphRenderAnchor(mockGraph as unknown as LGraph)

Or define a helper function that creates properly typed mock graphs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/core/layout/transform/graphRenderTransform.test.ts` around lines
89 - 99, Replace the unsafe "as never" casts in the tests with a proper typed
mock using TypeScript's "satisfies" so the mock maintains the Graph shape while
allowing partial fields; locate uses of getGraphRenderAnchor in this test (e.g.,
the mockGraph declarations around the anchor1/anchor2 and empty-graph cases) and
change those mockGraph objects to use "satisfies Graph" (or create a small
helper like createMockGraph(...) that returns a value typed as Graph via
satisfies) so the compiler validates the mock shape without forcing full
implementation.
src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.test.ts (1)

14-28: Minor semantic note: Point used for size property.

The size property represents width/height dimensions, but it's typed as Point (which semantically represents x/y coordinates). This works because both are [number, number] tuples, but could be confusing. Consider using a Size type alias or [number, number] directly for clarity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.test.ts`
around lines 14 - 28, The size property is currently typed as Point which
semantically implies coordinates; change its type to a Size alias or the
explicit tuple [number, number] to make intent clear: update the declaration of
size in the returned object (and any related type imports) from "size: [w, h] as
Point" to use a Size type or "[number, number]" and ensure getters/setters
(width, set width) and boundingRect continue to operate on the new Size type
without runtime changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/core/layout/store/layoutStore.ts`:
- Around line 920-922: batchUpdateNodeBounds temporarily sets the shared field
this.currentSource = LayoutSource.Vue and only restores it after
applyOperation() returns, but notifyChange(change) is called while the temporary
context is active so synchronous listeners inherit the wrong source/actor;
before calling notifyChange(change) restore the previous this.currentSource (and
any related actor/context fields) so listeners run with the original context, or
alternatively change the notification to pass an immutable operation context
object to listeners instead of relying on shared mutable fields (refer to
batchUpdateNodeBounds, this.currentSource, LayoutSource.Vue, applyOperation(),
and notifyChange()).

---

Outside diff comments:
In `@src/scripts/app.ts`:
- Around line 1316-1321: The helper ensureCorrectLayoutScale currently prefers
the rendererVersion parameter over graph.extra.workflowRendererVersion, which
can cause double-renormalization; update ensureCorrectLayoutScale so it first
reads graph.extra.workflowRendererVersion (use the parameter rendererVersion
only as a fallback when the graph metadata is missing), and add an early guard
that returns immediately if graph.extra.workflowRendererVersion ===
'Vue-corrected' to make the function idempotent; adjust callers like
ensureCorrectLayoutScale(originalMainGraphRenderer, this.rootGraph) only if they
rely on the old precedence.

---

Nitpick comments:
In `@browser_tests/tests/rendererToggleStability.spec.ts`:
- Around line 35-41: The test "node positions do not drift when toggling between
Vue and LiteGraph renderers" currently snapshots whatever graph is present at
startup; before calling getAllNodePositions(comfyPage) load a deterministic
workflow JSON from the assets/ directory (e.g., a premade fixture like
assets/<desired-workflow>.json) into the app using the test page helper (via
comfyPage import/upload or the app's load workflow API), wait for the workflow
to finish loading (nodes rendered), then proceed with the TOGGLE_COUNT loop and
assertions; update the test body around getAllNodePositions to perform this
deterministic load so positions are asserted against a known graph.

In `@src/renderer/core/layout/transform/graphRenderTransform.test.ts`:
- Around line 89-99: Replace the unsafe "as never" casts in the tests with a
proper typed mock using TypeScript's "satisfies" so the mock maintains the Graph
shape while allowing partial fields; locate uses of getGraphRenderAnchor in this
test (e.g., the mockGraph declarations around the anchor1/anchor2 and
empty-graph cases) and change those mockGraph objects to use "satisfies Graph"
(or create a small helper like createMockGraph(...) that returns a value typed
as Graph via satisfies) so the compiler validates the mock shape without forcing
full implementation.

In `@src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.test.ts`:
- Around line 14-28: The size property is currently typed as Point which
semantically implies coordinates; change its type to a Size alias or the
explicit tuple [number, number] to make intent clear: update the declaration of
size in the returned object (and any related type imports) from "size: [w, h] as
Point" to use a Size type or "[number, number]" and ensure getters/setters
(width, set width) and boundingRect continue to operate on the new Size type
without runtime changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 27dec7f7-a0ab-495a-9074-1f0a36907218

📥 Commits

Reviewing files that changed from the base of the PR and between dbd76a0 and 9e72a6e.

📒 Files selected for processing (10)
  • browser_tests/tests/rendererToggleStability.spec.ts
  • src/composables/graph/useVueNodeLifecycle.ts
  • src/lib/litegraph/src/LGraph.ts
  • src/platform/workflow/validation/schemas/workflowSchema.ts
  • src/renderer/core/layout/store/layoutStore.ts
  • src/renderer/core/layout/transform/graphRenderTransform.test.ts
  • src/renderer/core/layout/transform/graphRenderTransform.ts
  • src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.test.ts
  • src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts
  • src/scripts/app.ts
💤 Files with no reviewable changes (1)
  • src/composables/graph/useVueNodeLifecycle.ts

@DrJKL DrJKL added the preview label Mar 10, 2026
- Remove projectPoint/projectBounds (YAGNI — no production consumers)

- Extract unprojectPosSize helper to DRY repeated marshalling

- Remove redundant modeChanged guard in useVueNodeLifecycle watch

Amp-Thread-ID: https://ampcode.com/threads/T-019cd514-f65d-7569-b1c8-050d57523d7b
Co-authored-by: Amp <amp@ampcode.com>
Copy link
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: 2

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

Inline comments:
In `@src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts`:
- Around line 18-30: The helper unprojectPosSize currently replaces item.pos and
item.size with new arrays which breaks the shared-buffer invariant for
Rectangle/graph.groups; instead mutate the existing arrays in place: after
computing c via unprojectBounds(anchor, RENDER_SCALE_FACTOR) assign the computed
values into the existing item.pos and item.size entries (e.g. update
item.pos[0], item.pos[1], item.size[0], item.size[1]) rather than reassigning
the arrays so any shared backing buffer remains intact.
- Around line 55-56: The function ensureCorrectLayoutScale uses rendererVersion
?? graph.extra.workflowRendererVersion which lets a passed-in rendererVersion
override the graph's own 'Vue-corrected' marker; update the logic in
ensureCorrectLayoutScale (and the similar block around the renderer check at the
other occurrence) to first check graph.extra['Vue-corrected'] (or
graph.extra?.['Vue-corrected']) and return false if present before honoring
rendererVersion, so an already-normalized graph is not reprocessed even when a
fallback rendererVersion of 'Vue' is supplied.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fc5d8e47-336f-4bb5-9071-e020b29471da

📥 Commits

Reviewing files that changed from the base of the PR and between 9e72a6e and 751cd9b.

📒 Files selected for processing (4)
  • src/composables/graph/useVueNodeLifecycle.ts
  • src/renderer/core/layout/transform/graphRenderTransform.test.ts
  • src/renderer/core/layout/transform/graphRenderTransform.ts
  • src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/renderer/core/layout/transform/graphRenderTransform.test.ts
  • src/composables/graph/useVueNodeLifecycle.ts
  • src/renderer/core/layout/transform/graphRenderTransform.ts

DrJKL and others added 2 commits March 9, 2026 17:46
Move min-w to both outer container and inner wrapper via a shared CSS variable (--min-node-width) so overlays and node body stay in sync.

Extract MIN_NODE_WIDTH=225 constant from graphRenderTransform.ts and use it across LGraphNode.vue and useNodeResize.ts.

Amp-Thread-ID: https://ampcode.com/threads/T-019cd524-d504-764b-b685-2fd29c09a3b6
Co-authored-by: Amp <amp@ampcode.com>
When a node is collapsed in Vue mode, the DOM shrinks to header-only height. The ResizeObserver was writing this small size back to the layout store, permanently overwriting the stored expanded size. When uncollapsing in litegraph mode, the node would appear with minimized height.

Add data-collapsed attribute to tracked node elements and skip ResizeObserver bounds updates for collapsed nodes.

Amp-Thread-ID: https://ampcode.com/threads/T-019cd539-7557-7248-a7e1-4ea4ff254ed2
Co-authored-by: Amp <amp@ampcode.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts`:
- Around line 121-123: The early continue when a node is collapsed prevents
adding it to nodesNeedingSlotResync, leaving cached slot offsets stale; in
useVueNodeResizeTracking.ts (inside the loop that checks elementType and
element.dataset.collapsed) keep skipping the bounds write for collapsed nodes
but still enqueue them for slot resync by adding the node's id to
nodesNeedingSlotResync (or calling the same queuing logic used elsewhere) before
continuing — reference elementType, element.dataset.collapsed,
nodesNeedingSlotResync, and the related LGraphNode.vue behavior to ensure
collapsed nodes get their slot offsets recalculated on next sync.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3c61b872-f4af-49bd-8794-c292f31d4fe4

📥 Commits

Reviewing files that changed from the base of the PR and between 751cd9b and 31b8178.

📒 Files selected for processing (5)
  • src/renderer/core/layout/transform/graphRenderTransform.ts
  • src/renderer/extensions/vueNodes/components/LGraphNode.vue
  • src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts
  • src/renderer/extensions/vueNodes/interactions/resize/useNodeResize.test.ts
  • src/renderer/extensions/vueNodes/interactions/resize/useNodeResize.ts

christian-byrne and others added 7 commits March 9, 2026 22:59
Adds a concise guide to `docs/release-process.md` explaining how the
release workflows interact, with focus on the version semantics that
differ between minor and patch bumps.

Key sections:
- How minor bumps freeze the previous minor into `core/` and `cloud/`
branches
- How patch bumps on `main` vs `core/X.Y` differ (published vs draft
releases)
- Why unreleased commits are dual-homed when a minor bump happens
- Summary table, backporting, publishing, and bi-weekly automation

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9548-docs-add-release-process-guide-31d6d73d365081f2bdaace48a7cb81ae)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Alexander Brown <drjkl@comfy.org>
Main targeted, built on
#9551

## Summary

Fix Load Image/Load Video input dropdown tabs not showing available
input assets in Vue node select dropdown.

## Changes

- **What**: Keep combo widget `options` object identity while exposing
dynamic `values` for cloud/remote combos.
- **What**: Remove temporary debug logging and restore clearer dropdown
filter branching.
- **What**: Remove stale `searcher`/`updateKey` prop plumbing in
dropdown menu/actions and update related tests.

## Review Focus

Verify `Load Image` / `Load Video` Inputs tab behavior and confirm
cloud/remote combo option values still update correctly.

Relates to #9551

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9670-fix-show-load-widget-inputs-in-media-dropdown-31e6d73d36508148b845e18268a60c2a)
by [Unito](https://www.unito.io)

---------

Co-authored-by: bymyself <cbyrne@comfy.org>
Co-authored-by: Amp <amp@ampcode.com>
…ogs (#9672)

## Summary
Adds `isGraphReady` getter to `ComfyApp` and uses it in
`executionErrorStore` guards to prevent false 'ComfyApp graph accessed
before initialization' error logs during early store evaluation.

## Changes
- **What**: Added `isGraphReady` boolean getter to `ComfyApp` that
safely checks graph initialization without triggering the `rootGraph`
getter's error log. Updated 5 guard sites in `executionErrorStore` to
use `app.isGraphReady` instead of `app.rootGraph`.
- **Why**: The `rootGraph` getter logs an error when accessed before
initialization. Computed properties and watch callbacks in
`executionErrorStore` are evaluated early (before graph init), causing
false error noise in the console.

## Review Focus
- `isGraphReady` is intentionally minimal — just
`!!this.rootGraphInternal` — to avoid duplicating the error-logging
behavior of `rootGraph`
- The `watch(lastNodeErrors, ...)` callback now checks `isGraphReady` at
the top and early-returns, consistent with the computed property pattern

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9672-fix-add-isGraphReady-guard-to-prevent-premature-graph-access-error-logs-31e6d73d365081be8e1fc77114ce9382)
by [Unito](https://www.unito.io)

Co-authored-by: Alexander Brown <drjkl@comfy.org>
As a temporary fix for widgets being incorrectly hidden, #9669 allowed
all disabled widgets to be displayed.

This PR provides a more robust implementation to derive whether the
widget, as would be displayed from the root graph, is disabled.

Potential regression:
- Drag drop handlers are applied on node, not widgets. A subgraph
containing a "Load Image" node, does not allow dragging and dropping an
image onto the subgraphNode in order to load it. Because app mode
widgets would display from the original owning node prior to this PR,
these drag/drop handlers would apply. Placing "Load Image" nodes. I
believe this change makes behavior more consistent, but it warrants
consideration.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9671-Restore-hiding-of-linked-inputs-in-app-mode-31e6d73d365081688e37fbb931f3af68)
by [Unito](https://www.unito.io)
## Summary

Cloud build dispatch was only triggering on the `labeled` event, not on
subsequent pushes to PRs that already had a preview label.

## Changes

- **What**: Add `synchronize` to `pull_request` event types and update
the `if` condition to support all three preview labels (`preview`,
`preview-cpu`, `preview-gpu`). For `labeled` events, check the added
label name; for `synchronize` events, check existing PR labels.

## Review Focus

The `if` condition now branches on `github.event.action` to use the
correct label-checking mechanism for each event type.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9636-fix-dispatch-cloud-build-on-synchronize-for-preview-labeled-PRs-31e6d73d3650814e9069e37d6199ffc9)
by [Unito](https://www.unito.io)
Thumbnail downscaling is currently being used in more places than it
should be.
- Nodes which display images will display incorrect resolution
indicators
<img width="255" height="372" alt="image"
src="https://github.com/user-attachments/assets/674790b6-04c8-4db0-84c2-2fa2dbaf123d"
/> <img width="255" height="372" alt="image"
src="https://github.com/user-attachments/assets/1dbe751b-7462-4408-9236-9446b005f5fc"
/>

This is particularly confusing with output nodes, which claim the output
is not of the intended resolution
- The "Download Image" and "Open Image" context menu actions will
incorrectly download the downscaled thumbnail.
- The assets panel will incorrectly display the thumbnail resolution as
the resolution of the output
- The lightbox (zoom) of an image will incorrectly display a downscaled
thumbnail.

This PR is a quick workaround to staunch the major problems
- Nodes always display full previews.
- Resolution downscaling is applied on the assert card, not on the
assetItem itself
- Due to implementation, this means that asset cards will still
incorrectly show the resolution of the thumbnail instead of the size of
the full image.

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
- batch node drag updates through a single bounds transaction

- coalesce layout-to-litegraph sync work with requestAnimationFrame

- add node-scoped layout subscriptions and switch LGraphNode to onNodeChange

- add regression tests for batched drag and sync coalescing

Amp-Thread-ID: https://ampcode.com/threads/T-019cd561-5831-7688-af27-3701c92948f7
Co-authored-by: Amp <amp@ampcode.com>
Copy link
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: 5

🧹 Nitpick comments (2)
src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.test.ts (1)

22-39: Consider adding v-model props to MockFormDropdownMenu for completeness.

The mock component includes most props but omits the defineModel bindings that FormDropdownMenu exposes (e.g., filterSelected, layoutMode, sortSelected, searchQuery, ownershipSelected, baseModelSelected). While the current tests don't exercise these, future tests might benefit from having a more complete mock.

This is optional since current tests focus only on items reactivity.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.test.ts`
around lines 22 - 39, MockFormDropdownMenu currently defines props for items and
several filters but omits the v-model bindings exposed by the real
FormDropdownMenu; add props to MockFormDropdownMenu for filterSelected,
layoutMode, sortSelected, searchQuery, ownershipSelected, and baseModelSelected
(matching the real component's v-model names and types) so the mock can accept
and reflect v-model interactions in future tests, then ensure these props are
included in the props object of the MockFormDropdownMenu definition and
used/returned as needed in setup to mirror the real component's API.
src/renderer/core/layout/sync/useLayoutSync.ts (1)

76-76: Prefer for...of to avoid lint warning about callback return value.

Biome flags this because Set.add() returns the Set itself, and forEach callbacks shouldn't return values. While functionally correct, a for...of loop is cleaner and avoids the lint warning.

♻️ Suggested refactor
-      change.nodeIds.forEach((nodeId) => pendingNodeIds.add(nodeId))
+      for (const nodeId of change.nodeIds) {
+        pendingNodeIds.add(nodeId)
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/core/layout/sync/useLayoutSync.ts` at line 76, Replace the
forEach callback with a for...of loop to avoid returning values from the
callback; in useLayoutSync.ts, locate the code that iterates change.nodeIds and
calling pendingNodeIds.add(nodeId) and rewrite it to use: for (const nodeId of
change.nodeIds) { pendingNodeIds.add(nodeId) } so the lint warning about
callback return value is eliminated while keeping the same behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/cloud-dispatch-build.yaml:
- Around line 29-40: Update the PR guard expression in the if: condition to also
require that the PR head repo matches the base repo by adding a check for
github.event.pull_request.head.repo.full_name == github.repository so
fork-originated PRs are excluded (this prevents jobs from running on
'synchronize' and 'labeled' events for forks and avoids failing when
secrets.CLOUD_DISPATCH_TOKEN is unavailable). Ensure the new clause is combined
with the existing pull_request-specific branches (the labeled and synchronize
checks) so it only applies when github.event_name == 'pull_request'.

In `@docs/release-process.md`:
- Around line 25-29: The fenced code block containing the ASCII diagram (the
lines starting with "v1.40.1 ── A ── B ── C ── [bump to 1.41.0]" and the
following two lines) needs a language specifier to satisfy markdown linting;
update the opening fence from ``` to ```text so the block is ```text ... ``` and
retains the ASCII art unchanged.

In `@src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.test.ts`:
- Around line 341-348: The local variable named "constructor" shadows the
restricted global name and causes lint failure; rename that binding returned
from useComboWidget() (e.g., to comboConstructor, createComboWidget, or ctor)
and update its usage where you call it with mockNode and inputSpec (the places
around useComboWidget(), mockNode, inputSpec, and widget) so the test no longer
uses the reserved identifier.

In `@src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts`:
- Around line 51-66: The setter installed on widget.options.values currently
only accepts arrays and ignores function assignments, so update the
Object.defineProperty setter for 'values' to accept and store either an array or
a function: if the assigned value is an array set fallbackValues, if it's a
function set fallbackValues to that function (or store it in a new variable and
have get() call it via getValues); ensure getValues continues to resolve both
arrays and functions when returning values. Locate the Object.defineProperty for
options.values, the fallbackValues variable, and getValues to implement this
change so subsequent assignments like widget.options.values = fn are preserved.

In `@src/utils/litegraphUtil.ts`:
- Around line 323-346: resolveNodeWidget currently returns [] as soon as a node
is found but the named widget is missing; instead, when a node exists but widget
lookup fails you should still allow the later subgraph-promoted-widget search to
run and, if that also fails, return [node] as the fallback. Modify
resolveNodeWidget so that inside the "if (node) { ... }" block you only return
when you find the widget (return [node, widget]); if not found, do not return
immediately — let the subsequent loop that checks isPromotedWidgetView,
sourceWidgetName and sourceNodeId run; after that loop, if the original node
variable is present return [node], otherwise return [].

---

Nitpick comments:
In `@src/renderer/core/layout/sync/useLayoutSync.ts`:
- Line 76: Replace the forEach callback with a for...of loop to avoid returning
values from the callback; in useLayoutSync.ts, locate the code that iterates
change.nodeIds and calling pendingNodeIds.add(nodeId) and rewrite it to use: for
(const nodeId of change.nodeIds) { pendingNodeIds.add(nodeId) } so the lint
warning about callback return value is eliminated while keeping the same
behavior.

In
`@src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.test.ts`:
- Around line 22-39: MockFormDropdownMenu currently defines props for items and
several filters but omits the v-model bindings exposed by the real
FormDropdownMenu; add props to MockFormDropdownMenu for filterSelected,
layoutMode, sortSelected, searchQuery, ownershipSelected, and baseModelSelected
(matching the real component's v-model names and types) so the mock can accept
and reflect v-model interactions in future tests, then ensure these props are
included in the props object of the MockFormDropdownMenu definition and
used/returned as needed in setup to mirror the real component's API.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7361deca-04c0-4e52-bc2b-f56ef06db714

📥 Commits

Reviewing files that changed from the base of the PR and between 31b8178 and 9478582.

📒 Files selected for processing (29)
  • .github/workflows/cloud-dispatch-build.yaml
  • docs/release-process.md
  • src/components/builder/AppBuilder.vue
  • src/composables/graph/useVueNodeLifecycle.ts
  • src/extensions/core/imageCompare.ts
  • src/platform/assets/components/MediaAssetCard.vue
  • src/platform/assets/composables/media/assetMappers.ts
  • src/platform/assets/schemas/assetSchema.ts
  • src/platform/assets/utils/outputAssetUtil.ts
  • src/renderer/core/layout/operations/layoutMutations.ts
  • src/renderer/core/layout/store/layoutStore.test.ts
  • src/renderer/core/layout/store/layoutStore.ts
  • src/renderer/core/layout/sync/useLayoutSync.test.ts
  • src/renderer/core/layout/sync/useLayoutSync.ts
  • src/renderer/core/layout/types.ts
  • src/renderer/extensions/linearMode/LinearControls.vue
  • src/renderer/extensions/vueNodes/components/LGraphNode.vue
  • src/renderer/extensions/vueNodes/layout/useNodeDrag.test.ts
  • src/renderer/extensions/vueNodes/layout/useNodeDrag.ts
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenu.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuActions.vue
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.test.ts
  • src/renderer/extensions/vueNodes/widgets/composables/useComboWidget.ts
  • src/scripts/app.ts
  • src/stores/executionErrorStore.ts
  • src/stores/nodeOutputStore.ts
  • src/utils/litegraphUtil.ts
💤 Files with no reviewable changes (3)
  • src/stores/nodeOutputStore.ts
  • src/extensions/core/imageCompare.ts
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdownMenuActions.vue
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/scripts/app.ts

@DrJKL DrJKL marked this pull request as ready for review March 11, 2026 20:56
@dosubot dosubot bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Mar 11, 2026
AustinMroz
AustinMroz previously approved these changes Mar 12, 2026
Copy link
Collaborator

@AustinMroz AustinMroz left a comment

Choose a reason for hiding this comment

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

I'm not seeing any glaring issues from reading over the code.

As mentioned offline, I'm still able to trivially produce errors in node sizing by reloading while the browser tab is not in the foreground. This seems to be a browser specific issue as I can not reproduce in Firefox. Of note useDocumentVisibility reports that the document is visible during resizeObserver trigger.
image

I am specifically concerned that a 'code simplification PR' is adding ~6x the code it's removing.


In terms of a future ideal, is there ever a time that nodes should resize or move position without either

  1. Code that mutates the position of the node
  2. A user releasing a drag handle

Is having a resizeObserver at all actually needed?


// ResizeObserver emits entries where nothing changed (e.g. initial observe).
// Skip expensive DOM reads when this exact element/node already measured at
// the same normalized bounds and size.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I've tried multiple browsers, but I'm unable to replicate a scenario where entries which have not actually undergone a resize are included.

Copy link
Contributor

Choose a reason for hiding this comment

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

note (non-blocking): ResizeObserver does fire on initial observe per the spec (the first callback always includes the observed element even if no resize occurred). The guard here is a perf optimization to skip redundant DOM reads in that case. Harmless if it never triggers in practice, but it does match the spec behavior.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Needing to make changes to formdropdown tests as a result of node resize code seems really out of scope.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

🤷🏻

Copy link
Contributor

Choose a reason for hiding this comment

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

note (non-blocking): the mock simplification (defineComponent -> plain objects) was needed because the resize tracking changes affected how component mounting works in tests. The changes are test-only and add a missing coverage case. Agreed it is scope-adjacent - happy to split out if you feel strongly, but it should not block merge.

Copy link
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: 2

🧹 Nitpick comments (3)
src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue (1)

143-154: Keep isOpen tied to an actual popover action.

With the new optional chaining, isOpen still flips even when the imperative call is missing, so the component can report “open” or “closed” while the overlay did nothing. Guard the state update behind the method check instead of silently no-oping.

♻️ Suggested adjustment
 const toggleDropdown = (event: Event) => {
   if (disabled) return
-  if (popoverRef.value && triggerRef.value) {
-    popoverRef.value.toggle?.(event, triggerRef.value)
-    isOpen.value = !isOpen.value
-  }
+  const popover = popoverRef.value
+  if (!triggerRef.value || !popover?.toggle) return
+
+  popover.toggle(event, triggerRef.value)
+  isOpen.value = !isOpen.value
 }
 
 const closeDropdown = () => {
-  if (popoverRef.value) {
-    popoverRef.value.hide?.()
-    isOpen.value = false
-  }
+  const popover = popoverRef.value
+  if (!popover?.hide) return
+
+  popover.hide()
+  isOpen.value = false
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue`
around lines 143 - 154, toggleDropdown and closeDropdown update isOpen even when
the popover's imperative methods are missing due to optional chaining; change
them to first check that popoverRef.value?.toggle (in toggleDropdown) or
popoverRef.value?.hide (in closeDropdown) is a real function before calling it
and only then update isOpen.value (and still respect disabled and triggerRef).
In other words, for toggleDropdown verify popoverRef.value?.toggle exists, call
popoverRef.value.toggle(event, triggerRef.value) and then flip isOpen.value; for
closeDropdown verify popoverRef.value?.hide exists, call it and then set
isOpen.value = false.
src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.test.ts (1)

49-63: Derive the mount-helper props from FormDropdown instead of duplicating them.

MountDropdownOptions is re-declaring part of the component API here, so a prop signature change can leave the helper stale while the test still compiles. Using Partial<ComponentProps<typeof FormDropdown>> keeps this in lockstep with the component.

♻️ Suggested adjustment
+import type { ComponentProps } from 'vue-component-type-helpers'
 import FormDropdown from './FormDropdown.vue'
 import type { FormDropdownItem } from './types'
@@
-interface MountDropdownOptions {
-  searcher?: (
-    query: string,
-    items: FormDropdownItem[],
-    onCleanup: (cleanupFn: () => void) => void
-  ) => Promise<FormDropdownItem[]>
-  searchQuery?: string
-}
+type MountDropdownOptions = Partial<ComponentProps<typeof FormDropdown>>
@@
 function mountDropdown(
   items: FormDropdownItem[],
   options: MountDropdownOptions = {}
 ) {
   return mount(FormDropdown, {
-    props: { items, ...options },
+    props: { items, ...options } as ComponentProps<typeof FormDropdown>,

Based on learnings, "In test files (e.g., any file ending with .test.ts or .test.tsx), for test helpers like mountComponent, type the props parameter as Partial<ComponentProps>."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.test.ts`
around lines 49 - 63, MountDropdownOptions is duplicating FormDropdown's prop
types and can go out of sync; replace the custom interface with a derived type
using Partial<ComponentProps<typeof FormDropdown>> and update the mountDropdown
signature to accept options: Partial<ComponentProps<typeof FormDropdown>> so the
helper stays locked to FormDropdown's props (remove MountDropdownOptions and any
manual prop declarations and adjust the mount call accordingly).
src/renderer/core/layout/sync/useLayoutSync.ts (1)

30-35: Preserve string node IDs when resolving LiteGraph nodes.

LayoutChange.nodeIds are strings, and LiteGraph's NodeId allows number | string. Number.parseInt() is lossy ('foo'NaN, '12a'12), so any non-numeric ID will silently stop syncing here. Prefer the raw ID first and only fall back to numeric lookup when needed.

💡 Suggested fix
-      const liteNode = canvas.graph.getNodeById(Number.parseInt(nodeId, 10))
+      const numericNodeId = Number(nodeId)
+      const liteNode =
+        canvas.graph.getNodeById(nodeId) ??
+        (Number.isNaN(numericNodeId)
+          ? null
+          : canvas.graph.getNodeById(numericNodeId))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/core/layout/sync/useLayoutSync.ts` around lines 30 - 35, The
loop is converting string node IDs with Number.parseInt which drops non-numeric
IDs and breaks canvas.graph.getNodeById lookup; change the lookup in
useLayoutSync so it first calls canvas.graph.getNodeById(nodeId) using the raw
string from pendingNodeIds (or the value returned by
layoutStore.getNodeLayoutRef(nodeId).value), and only if that returns falsy
attempt a numeric lookup (e.g., parseInt(nodeId, 10)) as a fallback; update the
code paths around pendingNodeIds, layoutStore.getNodeLayoutRef, and
canvas.graph.getNodeById to preserve string IDs and only coerce to number when
necessary.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/TopMenuSection.test.ts`:
- Around line 569-594: The forEach callbacks in the test implicitly return a
value because they use concise arrow bodies like `(callback) => callback(0)`;
update these to use block arrow functions that call the callback without
returning (e.g., `(callback) => { callback(0); }`) for both the
initialCallbacks.forEach and callbacks.forEach invocations that use rafCallbacks
so the linter/static analysis warning is resolved.

In `@src/renderer/core/layout/sync/useLayoutSync.ts`:
- Around line 65-90: scheduleFlush currently enqueues microtasks via
queueMicrotask that cannot be canceled, so stale microtasks (from a previous
sync session) can call flushPendingChanges and operate on shared state
(pendingNodeIds) after stopSync()/startSync() restart sync; modify the sync
lifecycle to include a generation/epoch token (incremented in
startSync()/stopSync()) stored in a local variable when scheduling (in
scheduleFlush) and checked at the start of the microtask and the RAF callback
before calling flushPendingChanges, and also ensure rafId/isMicrotaskQueued are
reset when sync restarts so stale callbacks see a mismatched generation and bail
out; update references: scheduleFlush, rafId, isMicrotaskQueued, queueMicrotask,
requestAnimationFrame, flushPendingChanges, stopSync, startSync, and
pendingNodeIds.

---

Nitpick comments:
In `@src/renderer/core/layout/sync/useLayoutSync.ts`:
- Around line 30-35: The loop is converting string node IDs with Number.parseInt
which drops non-numeric IDs and breaks canvas.graph.getNodeById lookup; change
the lookup in useLayoutSync so it first calls canvas.graph.getNodeById(nodeId)
using the raw string from pendingNodeIds (or the value returned by
layoutStore.getNodeLayoutRef(nodeId).value), and only if that returns falsy
attempt a numeric lookup (e.g., parseInt(nodeId, 10)) as a fallback; update the
code paths around pendingNodeIds, layoutStore.getNodeLayoutRef, and
canvas.graph.getNodeById to preserve string IDs and only coerce to number when
necessary.

In
`@src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.test.ts`:
- Around line 49-63: MountDropdownOptions is duplicating FormDropdown's prop
types and can go out of sync; replace the custom interface with a derived type
using Partial<ComponentProps<typeof FormDropdown>> and update the mountDropdown
signature to accept options: Partial<ComponentProps<typeof FormDropdown>> so the
helper stays locked to FormDropdown's props (remove MountDropdownOptions and any
manual prop declarations and adjust the mount call accordingly).

In
`@src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue`:
- Around line 143-154: toggleDropdown and closeDropdown update isOpen even when
the popover's imperative methods are missing due to optional chaining; change
them to first check that popoverRef.value?.toggle (in toggleDropdown) or
popoverRef.value?.hide (in closeDropdown) is a real function before calling it
and only then update isOpen.value (and still respect disabled and triggerRef).
In other words, for toggleDropdown verify popoverRef.value?.toggle exists, call
popoverRef.value.toggle(event, triggerRef.value) and then flip isOpen.value; for
closeDropdown verify popoverRef.value?.hide exists, call it and then set
isOpen.value = false.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 574e330a-da95-47ab-abf1-0b625a50ff77

📥 Commits

Reviewing files that changed from the base of the PR and between f06c9cd and b86949b.

📒 Files selected for processing (11)
  • src/components/TopMenuSection.test.ts
  • src/components/TopMenuSection.vue
  • src/platform/workflow/validation/schemas/workflowSchema.ts
  • src/renderer/core/layout/store/layoutStore.test.ts
  • src/renderer/core/layout/store/layoutStore.ts
  • src/renderer/core/layout/sync/useLayoutSync.test.ts
  • src/renderer/core/layout/sync/useLayoutSync.ts
  • src/renderer/extensions/vueNodes/components/LGraphNode.vue
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.test.ts
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue
  • src/scripts/app.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/platform/workflow/validation/schemas/workflowSchema.ts
  • src/scripts/app.ts

Copy link
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.

🧹 Nitpick comments (1)
src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue (1)

143-156: Optional chaining is redundant but harmless.

The popoverRef.value existence is already checked on lines 145 and 152. PrimeVue's Popover should always expose toggle and hide methods, so the optional chaining is technically unnecessary. However, this defensive pattern is acceptable for robustness against edge cases or future API changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue`
around lines 143 - 156, The optional chaining on popoverRef.value.toggle and
popoverRef.value.hide is redundant because you already check popoverRef.value
exists; update toggleDropdown and closeDropdown to call
popoverRef.value.toggle(event, triggerRef.value) and popoverRef.value.hide()
respectively (remove the ?.), leaving the existing checks for popoverRef.value
and triggerRef/value checks and the isOpen state toggles intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue`:
- Around line 143-156: The optional chaining on popoverRef.value.toggle and
popoverRef.value.hide is redundant because you already check popoverRef.value
exists; update toggleDropdown and closeDropdown to call
popoverRef.value.toggle(event, triggerRef.value) and popoverRef.value.hide()
respectively (remove the ?.), leaving the existing checks for popoverRef.value
and triggerRef/value checks and the isOpen state toggles intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: cbe7fa30-a849-4d8c-9fee-c5587eb3aa4f

📥 Commits

Reviewing files that changed from the base of the PR and between b70eb1f and eefdebd.

📒 Files selected for processing (1)
  • src/renderer/extensions/vueNodes/widgets/components/form/dropdown/FormDropdown.vue

Copy link
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: 2

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

Inline comments:
In `@browser_tests/tests/rendererToggleStability.spec.ts`:
- Around line 39-40: The test is reading graph state via
getAllNodePositions(comfyPage) without loading a deterministic workflow; load a
premade JSON workflow from assets before taking the baseline snapshot. Update
the test to load a known workflow (e.g., by calling a helper that uploads/parses
an assets/*.json file or by using comfyPage.evaluate to call
window.app.loadWorkflow / set window.app.graph) prior to calling
getAllNodePositions(comfyPage), so getAllNodePositions and comfyPage operate on
a stable graph state.
- Around line 52-53: The assertions using toBeCloseTo(initial.x, 1) and
toBeCloseTo(initial.y, 1) are using 1 decimal-place precision, not an absolute
±1 tolerance; update the checks (for variables current and initial in the
rendererToggleStability spec) to explicit absolute-tolerance assertions, e.g.
replace expect(current!.x).toBeCloseTo(initial.x, 1) and
expect(current!.y).toBeCloseTo(initial.y, 1) (and the similar pair later) with
assertions that use Math.abs(current!.x - initial.x) <= 1 and
Math.abs(current!.y - initial.y) <= 1 (or the test framework equivalent to
assert absolute difference <= 1) so the position drift check uses an absolute ±1
tolerance.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e9ebad8a-db89-48c0-90b9-91a0106b347a

📥 Commits

Reviewing files that changed from the base of the PR and between eefdebd and 516e012.

📒 Files selected for processing (1)
  • browser_tests/tests/rendererToggleStability.spec.ts

- clear stale node-scoped listeners during layout reinit

- guard null subgraph IO nodes during scale normalization

- document graph render anchor cache lifetime

- strengthen drag and renderer-toggle regression tests

Amp-Thread-ID: https://ampcode.com/threads/T-019ce5d7-168f-736b-bc01-dd46b66b5d71
Co-authored-by: Amp <amp@ampcode.com>
Copy link
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: 2

🧹 Nitpick comments (1)
src/renderer/extensions/vueNodes/layout/useNodeDrag.test.ts (1)

9-15: Reuse the real layout type in this mock map.

The inline { position; size } shape can drift from NodeLayout and let the suite compile against the wrong contract. Typing this as Map<string, Pick<NodeLayout, 'position' | 'size'>> keeps the mock coupled to the production API.

💡 Suggested cleanup
 import { beforeEach, describe, expect, it, vi } from 'vitest'
 import { ref } from 'vue'
 import type { Ref } from 'vue'
+import type { NodeLayout } from '@/renderer/core/layout/types'
 
 const testState = vi.hoisted(() => {
   return {
     selectedNodeIds: null as unknown as Ref<Set<string>>,
     selectedItems: null as unknown as Ref<unknown[]>,
-    nodeLayouts: new Map<
-      string,
-      {
-        position: { x: number; y: number }
-        size: { width: number; height: number }
-      }
-    >(),
+    nodeLayouts: new Map<string, Pick<NodeLayout, 'position' | 'size'>>(),

Based on learnings, in TypeScript test files, "avoid duplicating interface/type definitions. Import real type definitions from the component modules under test and reference them directly, so there is a single source of truth and to prevent type drift."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/extensions/vueNodes/layout/useNodeDrag.test.ts` around lines 9 -
15, The mock nodeLayouts Map is typed with an inline shape that can drift from
the real API; import the production NodeLayout type (e.g., import { NodeLayout }
from the module under test) and change the declaration of nodeLayouts to use
Map<string, Pick<NodeLayout, 'position' | 'size'>> so the test's mock stays tied
to the real NodeLayout contract (update the nodeLayouts variable/type
accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/renderer/core/layout/sync/useLayoutSync.ts`:
- Line 112: The forEach callback returns the Set from
pendingNodeIds.add(nodeId), which trips lint rule
lint/suspicious/useIterableCallbackReturn; update the callback in useLayoutSync
(the change.nodeIds.forEach(...) call) to use a block-bodied function that
explicitly performs pendingNodeIds.add(nodeId) and does not return a value (or
replace the forEach with a simple for...of loop over change.nodeIds that calls
pendingNodeIds.add(nodeId)), ensuring the callback returns void so the linter no
longer flags it.
- Around line 31-63: Replace the numeric coercion when looking up LiteGraph
nodes: stop using Number.parseInt(nodeId, 10) in the loop and pass nodeId
directly to canvas.graph.getNodeById(...) so string UUIDs and numeric string IDs
work correctly; keep the rest of the logic that reads layout from
layoutStore.getNodeLayoutRef(nodeId).value and updates liteNode.pos/size and
calls liteNode.onResize. Also fix the Biome lint issue by ensuring you don't
return from a Set.forEach callback (replace any pendingNodeIds.forEach(...) that
returns pendingNodeIds.add(nodeId) with a loop or a direct
pendingNodeIds.add(nodeId) statement).

---

Nitpick comments:
In `@src/renderer/extensions/vueNodes/layout/useNodeDrag.test.ts`:
- Around line 9-15: The mock nodeLayouts Map is typed with an inline shape that
can drift from the real API; import the production NodeLayout type (e.g., import
{ NodeLayout } from the module under test) and change the declaration of
nodeLayouts to use Map<string, Pick<NodeLayout, 'position' | 'size'>> so the
test's mock stays tied to the real NodeLayout contract (update the nodeLayouts
variable/type accordingly).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 769ecfb2-4cec-4d90-bdaf-ad5733581ed9

📥 Commits

Reviewing files that changed from the base of the PR and between eefdebd and afd9445.

📒 Files selected for processing (9)
  • browser_tests/tests/rendererToggleStability.spec.ts
  • src/renderer/core/layout/operations/layoutMutations.ts
  • src/renderer/core/layout/store/layoutStore.test.ts
  • src/renderer/core/layout/store/layoutStore.ts
  • src/renderer/core/layout/sync/useLayoutSync.ts
  • src/renderer/core/layout/transform/graphRenderTransform.ts
  • src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.test.ts
  • src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts
  • src/renderer/extensions/vueNodes/layout/useNodeDrag.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • browser_tests/tests/rendererToggleStability.spec.ts
  • src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.test.ts

Copy link
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.

Caution

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

⚠️ Outside diff range comments (1)
src/renderer/core/layout/sync/useLayoutSync.ts (1)

101-106: ⚠️ Potential issue | 🟠 Major

Move teardown before the null-canvas guard in startSync().

If startSync() is called with a null canvas during a canvas switch/teardown path, the current early return skips stopSync() and can leave the previous subscription active against a stale canvas instance.

💡 Proposed fix
 function startSync(canvas: ReturnType<typeof useCanvasStore>['canvas']) {
-  if (!canvas?.graph) return
-
   // Cancel last subscription
   stopSync()
+  if (!canvas?.graph) return
   // Subscribe to layout changes
   unsubscribe.value = layoutStore.onChange((change) => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/renderer/core/layout/sync/useLayoutSync.ts` around lines 101 - 106, The
null-canvas guard in startSync skips stopSync() and can leave an old
subscription active; move the teardown call so stopSync() is invoked before the
early return check. Specifically, in function startSync (and any related startup
path that uses useCanvasStore()['canvas']), call stopSync() first to cancel the
previous subscription, then check if (!canvas?.graph) return; and only after
that proceed to subscribe to layout changes. This ensures previous subscriptions
are cleaned up even when startSync is invoked with a null/stale canvas.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@src/renderer/core/layout/sync/useLayoutSync.ts`:
- Around line 101-106: The null-canvas guard in startSync skips stopSync() and
can leave an old subscription active; move the teardown call so stopSync() is
invoked before the early return check. Specifically, in function startSync (and
any related startup path that uses useCanvasStore()['canvas']), call stopSync()
first to cancel the previous subscription, then check if (!canvas?.graph)
return; and only after that proceed to subscribe to layout changes. This ensures
previous subscriptions are cleaned up even when startSync is invoked with a
null/stale canvas.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 268c7a7c-7da1-451a-8ce1-05ee1778b02e

📥 Commits

Reviewing files that changed from the base of the PR and between afd9445 and 8842d76.

📒 Files selected for processing (2)
  • src/renderer/core/layout/sync/useLayoutSync.ts
  • src/renderer/extensions/vueNodes/layout/useNodeDrag.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/renderer/extensions/vueNodes/layout/useNodeDrag.test.ts

Copy link
Contributor

@christian-byrne christian-byrne left a comment

Choose a reason for hiding this comment

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

All blocking feedback addressed and verified in code. Two non-blocking threads from AustinMroz remain open (ResizeObserver guard justification, FormDropdown test scope) - replied with rationale, neither blocks merge.

@DrJKL DrJKL merged commit 1280d41 into main Mar 13, 2026
33 checks passed
@DrJKL DrJKL deleted the drjkl/dark-energy branch March 13, 2026 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants