Skip to content

feat: centralize link topology state and compatibility boundaries#9247

Draft
DrJKL wants to merge 40 commits intomainfrom
drjkl/he-come-to-town
Draft

feat: centralize link topology state and compatibility boundaries#9247
DrJKL wants to merge 40 commits intomainfrom
drjkl/he-come-to-town

Conversation

@DrJKL
Copy link
Contributor

@DrJKL DrJKL commented Feb 26, 2026

Summary

Centralizes link/slot topology behavior behind store-driven read/write and compatibility adapters, then narrows legacy slot-repair to migration-only mismatch cases.

Changes

  • What:
    • Added graph-scoped link store lifecycle rehydration and store-backed topology reads.
    • Centralized disconnect/connect/subgraph IO mutation paths.
    • Normalized slot identity to canonical per-side names with legacy compatibility handling.
    • Extracted shared subgraph boundary remap and persistence/remap adapters.
    • Unified lifecycle callback/trigger dispatch via shared dispatcher boundary.
    • Narrowed fixLinkInputSlots runtime usage to explicit legacy mismatch detection.

Review Focus

  • Compatibility parity for callback/event ordering across connect/disconnect/subgraph flows.
  • Canonical slot identity behavior and legacy duplicate-name normalization.
  • Runtime boundary for legacy repair (hasLegacyLinkInputSlotMismatch + guarded repair invoke).
  • Persistence/remap adapter isolation from core mutation logic.

Validation

  • pnpm exec vitest run for:
    • src/lib/litegraph/src/LGraph.test.ts
    • src/lib/litegraph/src/subgraph/SubgraphIO.test.ts
    • src/lib/litegraph/src/LGraphNode.test.ts
    • src/lib/litegraph/src/canvas/LinkConnector.integration.test.ts
    • src/lib/litegraph/src/LGraphCanvas.clipboard.test.ts
    • src/utils/litegraphUtil.test.ts
    • src/scripts/app.test.ts
  • pnpm exec eslint --no-warn-ignored on touched litegraph + app files
  • pnpm exec oxlint on touched litegraph + app files
  • pnpm typecheck

Screenshots (if applicable)

N/A

DrJKL and others added 10 commits February 25, 2026 21:57
- add passive LinkStore for links, floating links, and reroutes

- rehydrate LinkStore on graph clear and configure lifecycle paths

- add unit and browser tests for ChangeTracker/link topology undo-redo parity

Amp-Thread-ID: https://ampcode.com/threads/T-019c9840-2f49-701c-bdf2-ae7f2f50acef
Co-authored-by: Amp <amp@ampcode.com>
@github-actions
Copy link

github-actions bot commented Feb 26, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 03/05/2026, 02:37:22 AM UTC

Links

@github-actions
Copy link

github-actions bot commented Feb 26, 2026

🎭 Playwright: ❌ 543 passed, 3 failed · 6 flaky

❌ Failed Tests

📊 Browser Reports
  • chromium: View Report (✅ 530 / ❌ 3 / ⚠️ 6 / ⏭️ 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)

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Centralizes link topology in a Pinia LinkStore, adds GraphLifecycleEventDispatcher and GraphPersistenceAdapter, introduces subgraph boundary adapters and canonical slot naming, shifts connect/disconnect flows to dispatcher-driven LGraph APIs, delegates clipboard remapping to the persistence adapter, and expands tests across link projection, undo/redo, subgraph IO, and lifecycle ordering.

Changes

Cohort / File(s) Summary
Store & Persistence
src/stores/linkStore.ts, src/lib/litegraph/src/infrastructure/GraphPersistenceAdapter.ts
Add Pinia per-graph LinkStore (rehydrate/clear/get helpers) and GraphPersistenceAdapter (toConfiguredTopology, patchLinkNodeIds, remapProxyWidgets) for serialization and remapping.
Lifecycle Dispatcher
src/lib/litegraph/src/infrastructure/GraphLifecycleEventDispatcher.ts
Introduce graphLifecycleEventDispatcher with dispatchConnectNodePair, dispatchDisconnectNodePair, dispatchSlotLinkChanged, dispatchNodeConnectionChange to centralize node/slot lifecycle notifications.
Core Graph API
src/lib/litegraph/src/LGraph.ts, src/lib/litegraph/src/LLink.ts, src/lib/litegraph/src/Reroute.ts
LGraph gets linkStoreKey, rehydrateLinkStore, delegates getLink/getReroute to LinkStore, and adds connect/disconnect/finalize and subgraph boundary helpers. LLink/Reroute now resolve links via network.getLink accessor.
Node & Subgraph Logic
src/lib/litegraph/src/LGraphNode.ts, src/lib/litegraph/src/subgraph/..., src/lib/litegraph/src/subgraph/SubgraphNode.ts
Connection/disconnection flows refactored to call LGraph APIs and dispatch lifecycle events; Subgraph I/O uses canonical slot naming and emits canonicalName in rename events.
Subgraph Boundary Utils
src/lib/litegraph/src/subgraph/subgraphUtils.ts
Add subgraphBoundaryAdapter and supporting types to detect/remap/resolve boundary links for convert/unpack flows.
Canvas / Clipboard
src/lib/litegraph/src/LGraphCanvas.ts, src/lib/litegraph/src/LGraphCanvas.clipboard.test.ts
Clipboard remap delegates to graphPersistenceAdapter.patchLinkNodeIds and remapProxyWidgets; removed inline helper implementations; tests updated.
Configure Orchestration & Utilities
src/utils/graphConfigureUtil.ts, src/utils/litegraphUtil.ts, src/utils/*test.ts
Introduce addAfterConfigureHandler to orchestrate post-configure repairs/sync; export hasLegacyLinkInputSlotMismatch and keep fixLinkInputSlots (now emitting deprecation warning); add tests.
Tests, Setup & App Integration
vitest.setup.ts, src/lib/litegraph/src/...test.ts, browser_tests/tests/..., src/scripts/changeTracker.test.ts, src/scripts/app.ts
Enable Pinia test setup (createTestingPinia with stubActions:false); many new/updated tests for link-store projection, undo/redo, callback ordering, subgraph IO, clipboard behavior; app uses external addAfterConfigureHandler.
Link Store Consumers & Test Mocks
src/lib/litegraph/src/LGraph.test.ts, src/lib/litegraph/src/LLink.test.ts, src/lib/litegraph/src/LLink.ts
Tests and code updated to reference useLinkStore and exported graph components; LLink tests added to verify getLink-based resolution and resolveMany behavior.
Infrastructure Types & Events
src/lib/litegraph/src/infrastructure/SubgraphEventMap.ts
Add optional canonicalName?: string to subgraph renaming input/output event payloads.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant LGraph
    participant LinkStore
    participant Dispatcher as GraphLifecycleEventDispatcher
    participant SourceNode
    participant TargetNode

    Client->>LGraph: connectSlots(source,outIdx,target,inIdx,afterReroute?)
    LGraph->>LinkStore: create/register link (rehydrate topology)
    LGraph->>Dispatcher: dispatchConnectNodePair({source,outIdx,target,inIdx,link})
    Dispatcher->>SourceNode: onConnectionsChange(OUTPUT,outIdx,link)
    Dispatcher->>TargetNode: onConnectionsChange(INPUT,inIdx,link)
    Dispatcher->>Dispatcher: dispatchSlotLinkChanged (emit node:slot-links:changed if widget)
    LGraph->>LGraph: finalizeConnectedLink(link) (remove floating links / propagate reroutes)
    LGraph->>LinkStore: rehydrate/post-change sync
    LGraph->>Client: return link
Loading
sequenceDiagram
    participant User
    participant SubgraphInput
    participant LGraph
    participant LinkStore
    participant Dispatcher as GraphLifecycleEventDispatcher
    participant Node

    User->>SubgraphInput: connect(node, slotIndex)
    SubgraphInput->>LGraph: connectSubgraphInputSlot(...)
    LGraph->>LinkStore: create/register link
    LGraph->>Dispatcher: dispatchNodeConnectionChange({node, INPUT, slotIndex, connected:true, link})
    Dispatcher->>Node: onConnectionsChange(INPUT, slotIndex, link)
    SubgraphInput->>User: return link
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

size:XXL, preview

Suggested reviewers

  • christian-byrne
  • AustinMroz
  • pythongosssss

Poem

🐇 I hopped through maps and patched a link,
The dispatcher whispered—connect in sync.
Canonical names tidy each slot,
Clipboard IDs remapped on the spot,
A rabbit cheers: topology, wink!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.90% 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 PR title 'feat: centralize link topology state and compatibility boundaries' accurately summarizes the main architectural change of centralizing link/slot topology management through stores and adapters.
Description check ✅ Passed The PR description follows the template structure with Summary, Changes, and Review Focus sections. It provides clear context on what was changed (link store lifecycle, centralized mutation paths, canonical slot naming, adapters) and validation steps.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch drjkl/he-come-to-town

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

@github-actions
Copy link

github-actions bot commented Feb 26, 2026

📦 Bundle: 4.49 MB gzip 🔴 +2.1 kB

Details

Summary

  • Raw size: 21.1 MB baseline 21.1 MB — 🔴 +8.67 kB
  • Gzip: 4.49 MB baseline 4.49 MB — 🔴 +2.1 kB
  • Brotli: 3.47 MB baseline 3.47 MB — 🔴 +1.25 kB
  • Bundles: 228 current • 228 baseline • 132 added / 132 removed

Category Glance
Data & Services 🔴 +8.68 kB (2.74 MB) · Vendor & Third-Party 🟢 -12 B (8.86 MB) · Other ⚪ 0 B (7.92 MB) · Graph Workspace ⚪ 0 B (913 kB) · Panels & Settings ⚪ 0 B (436 kB) · Views & Navigation ⚪ 0 B (72.4 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-6tggHuEB.js (new) 17.8 kB 🔴 +17.8 kB 🔴 +6.27 kB 🔴 +5.46 kB
assets/index-DZ-kzSaI.js (removed) 17.8 kB 🟢 -17.8 kB 🟢 -6.28 kB 🟢 -5.46 kB

Status: 1 added / 1 removed

Graph Workspace — 913 kB (baseline 913 kB) • ⚪ 0 B

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-CaOwmQRX.js (removed) 913 kB 🟢 -913 kB 🟢 -194 kB 🟢 -148 kB
assets/GraphView-DyaQr89g.js (new) 913 kB 🔴 +913 kB 🔴 +194 kB 🔴 +147 kB

Status: 1 added / 1 removed

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

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-DpGfoZZU.js (removed) 15.5 kB 🟢 -15.5 kB 🟢 -3.32 kB 🟢 -2.82 kB
assets/CloudSurveyView-DR16hN3o.js (new) 15.5 kB 🔴 +15.5 kB 🔴 +3.32 kB 🔴 +2.81 kB
assets/CloudLoginView-DCN1MxbC.js (new) 11.5 kB 🔴 +11.5 kB 🔴 +3.21 kB 🔴 +2.85 kB
assets/CloudLoginView-DvcTbPtM.js (removed) 11.5 kB 🟢 -11.5 kB 🟢 -3.21 kB 🟢 -2.84 kB
assets/CloudSignupView-BTe5czPO.js (new) 9.41 kB 🔴 +9.41 kB 🔴 +2.72 kB 🔴 +2.39 kB
assets/CloudSignupView-ngcBfdYS.js (removed) 9.41 kB 🟢 -9.41 kB 🟢 -2.71 kB 🟢 -2.39 kB
assets/UserCheckView-BG-Kr9pq.js (new) 8.41 kB 🔴 +8.41 kB 🔴 +2.23 kB 🔴 +1.94 kB
assets/UserCheckView-FcS8QGsH.js (removed) 8.41 kB 🟢 -8.41 kB 🟢 -2.23 kB 🟢 -1.94 kB
assets/CloudLayoutView-C1HJ2zwS.js (removed) 6.47 kB 🟢 -6.47 kB 🟢 -2.12 kB 🟢 -1.86 kB
assets/CloudLayoutView-f8Ofkxfb.js (new) 6.47 kB 🔴 +6.47 kB 🔴 +2.12 kB 🔴 +1.86 kB
assets/CloudForgotPasswordView-B5LG1GMi.js (removed) 5.6 kB 🟢 -5.6 kB 🟢 -1.95 kB 🟢 -1.72 kB
assets/CloudForgotPasswordView-DpXvI_PR.js (new) 5.6 kB 🔴 +5.6 kB 🔴 +1.96 kB 🔴 +1.72 kB
assets/CloudAuthTimeoutView-DJ2nxUpy.js (removed) 4.95 kB 🟢 -4.95 kB 🟢 -1.79 kB 🟢 -1.57 kB
assets/CloudAuthTimeoutView-MzFv_FNP.js (new) 4.95 kB 🔴 +4.95 kB 🔴 +1.79 kB 🔴 +1.57 kB
assets/CloudSubscriptionRedirectView-BagB0s2z.js (removed) 4.8 kB 🟢 -4.8 kB 🟢 -1.81 kB 🟢 -1.6 kB
assets/CloudSubscriptionRedirectView-DxSbw3nY.js (new) 4.8 kB 🔴 +4.8 kB 🔴 +1.81 kB 🔴 +1.6 kB
assets/UserSelectView-BJAe8d6L.js (new) 4.5 kB 🔴 +4.5 kB 🔴 +1.64 kB 🔴 +1.47 kB
assets/UserSelectView-CJR6JaWT.js (removed) 4.5 kB 🟢 -4.5 kB 🟢 -1.64 kB 🟢 -1.47 kB
assets/layout-CTBdQNLF.js (removed) 296 B 🟢 -296 B 🟢 -225 B 🟢 -215 B
assets/layout-DHk8MpHy.js (new) 296 B 🔴 +296 B 🔴 +223 B 🔴 +193 B
assets/CloudSorryContactSupportView-DwQRNUxH.js 1.02 kB 1.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 10 added / 10 removed

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

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SecretsPanel-Bd440vPo.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +5.3 kB 🔴 +4.65 kB
assets/SecretsPanel-CqDR-9hr.js (removed) 21.5 kB 🟢 -21.5 kB 🟢 -5.3 kB 🟢 -4.65 kB
assets/LegacyCreditsPanel-B2JUFO6c.js (removed) 20.7 kB 🟢 -20.7 kB 🟢 -5.59 kB 🟢 -4.92 kB
assets/LegacyCreditsPanel-DsK1eIy2.js (new) 20.7 kB 🔴 +20.7 kB 🔴 +5.59 kB 🔴 +4.91 kB
assets/SubscriptionPanel-BtmLYZCk.js (new) 18.7 kB 🔴 +18.7 kB 🔴 +4.75 kB 🔴 +4.18 kB
assets/SubscriptionPanel-CefiC2pG.js (removed) 18.7 kB 🟢 -18.7 kB 🟢 -4.74 kB 🟢 -4.18 kB
assets/KeybindingPanel-BjKRx-AE.js (new) 12.3 kB 🔴 +12.3 kB 🔴 +3.53 kB 🔴 +3.13 kB
assets/KeybindingPanel-Bs5jNjRa.js (removed) 12.3 kB 🟢 -12.3 kB 🟢 -3.54 kB 🟢 -3.13 kB
assets/AboutPanel-Q5cowDm2.js (new) 9.79 kB 🔴 +9.79 kB 🔴 +2.73 kB 🔴 +2.45 kB
assets/AboutPanel-TamKwal2.js (removed) 9.79 kB 🟢 -9.79 kB 🟢 -2.73 kB 🟢 -2.47 kB
assets/ExtensionPanel-6nLELaLf.js (new) 9.42 kB 🔴 +9.42 kB 🔴 +2.67 kB 🔴 +2.38 kB
assets/ExtensionPanel-CZrGKHkC.js (removed) 9.42 kB 🟢 -9.42 kB 🟢 -2.67 kB 🟢 -2.39 kB
assets/ServerConfigPanel-Dy8DWTSh.js (removed) 6.49 kB 🟢 -6.49 kB 🟢 -2.13 kB 🟢 -1.92 kB
assets/ServerConfigPanel-zRMkzOZx.js (new) 6.49 kB 🔴 +6.49 kB 🔴 +2.13 kB 🔴 +1.92 kB
assets/UserPanel-BNiASuXg.js (new) 6.21 kB 🔴 +6.21 kB 🔴 +2.01 kB 🔴 +1.76 kB
assets/UserPanel-BP2vNxHM.js (removed) 6.21 kB 🟢 -6.21 kB 🟢 -2.02 kB 🟢 -1.77 kB
assets/cloudRemoteConfig-CYhKo9Pk.js (removed) 1.48 kB 🟢 -1.48 kB 🟢 -730 B 🟢 -632 B
assets/cloudRemoteConfig-gNnh7oVi.js (new) 1.48 kB 🔴 +1.48 kB 🔴 +727 B 🔴 +633 B
assets/refreshRemoteConfig-4gwnI-yZ.js (new) 1.14 kB 🔴 +1.14 kB 🔴 +520 B 🔴 +482 B
assets/refreshRemoteConfig-EO7IHOc6.js (removed) 1.14 kB 🟢 -1.14 kB 🟢 -521 B 🟢 -472 B
assets/config-Dim-BcsI.js 996 B 996 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Az4QGSr7.js 38.5 kB 38.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-Bc1kL5ht.js 27.8 kB 27.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BxoUe5GJ.js 28.7 kB 28.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-ClhpchbB.js 34.2 kB 34.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-D9qrBXiF.js 30.5 kB 30.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DXfL5vCm.js 23.9 kB 23.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-jzj0F3XL.js 28.8 kB 28.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-KcouHUkA.js 27.9 kB 27.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-MfieTybP.js 32.4 kB 32.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-V6oR26kh.js 24.5 kB 24.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-YUrYiWmR.js 29.9 kB 29.9 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-BGyob0Zr.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.18 kB 🔴 +994 B
assets/auth-C78DqOrD.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.18 kB 🟢 -991 B
assets/SignUpForm-c1LRKL_8.js (removed) 3.01 kB 🟢 -3.01 kB 🟢 -1.23 kB 🟢 -1.09 kB
assets/SignUpForm-wLXV9haA.js (new) 3.01 kB 🔴 +3.01 kB 🔴 +1.23 kB 🔴 +1.1 kB
assets/UpdatePasswordContent-DB-hXeVp.js (removed) 2.41 kB 🟢 -2.41 kB 🟢 -1.09 kB 🟢 -958 B
assets/UpdatePasswordContent-DosuKyqm.js (new) 2.41 kB 🔴 +2.41 kB 🔴 +1.09 kB 🔴 +955 B
assets/firebaseAuthStore-BZnm7pg3.js (new) 831 B 🔴 +831 B 🔴 +404 B 🔴 +356 B
assets/firebaseAuthStore-zAIhwkP3.js (removed) 831 B 🟢 -831 B 🟢 -408 B 🟢 -356 B
assets/auth-B5MWfM0n.js (new) 357 B 🔴 +357 B 🔴 +221 B 🔴 +195 B
assets/auth-Bf6ySJYe.js (removed) 357 B 🟢 -357 B 🟢 -225 B 🟢 -214 B
assets/PasswordFields-C2IR8Uml.js 4.51 kB 4.51 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WorkspaceProfilePic-KL8Hs2yn.js 1.57 kB 1.57 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 5 added / 5 removed

Editors & Dialogs — 779 B (baseline 779 B) • ⚪ 0 B

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useSubscriptionDialog-Bhm69hD2.js (removed) 779 B 🟢 -779 B 🟢 -399 B 🟢 -343 B
assets/useSubscriptionDialog-CTmf9lby.js (new) 779 B 🔴 +779 B 🔴 +397 B 🔴 +341 B

Status: 1 added / 1 removed

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

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useTerminalTabs-5-59yHo-.js (removed) 9.89 kB 🟢 -9.89 kB 🟢 -3.42 kB 🟢 -3 kB
assets/useTerminalTabs-vTHhpuQX.js (new) 9.89 kB 🔴 +9.89 kB 🔴 +3.42 kB 🔴 +3.01 kB
assets/ComfyQueueButton-96ycf-d0.js (removed) 8.02 kB 🟢 -8.02 kB 🟢 -2.49 kB 🟢 -2.23 kB
assets/ComfyQueueButton-CYGYMAQZ.js (new) 8.02 kB 🔴 +8.02 kB 🔴 +2.49 kB 🔴 +2.22 kB
assets/TopbarBadge-D1EvgBfE.js (removed) 7.39 kB 🟢 -7.39 kB 🟢 -1.8 kB 🟢 -1.59 kB
assets/TopbarBadge-Tnm26nM6.js (new) 7.39 kB 🔴 +7.39 kB 🔴 +1.8 kB 🔴 +1.59 kB
assets/ScrubableNumberInput-BS8_8a5j.js (removed) 5.95 kB 🟢 -5.95 kB 🟢 -2.06 kB 🟢 -1.83 kB
assets/ScrubableNumberInput-eIQKSXsl.js (new) 5.95 kB 🔴 +5.95 kB 🔴 +2.06 kB 🔴 +1.83 kB
assets/FormSearchInput-CkLtPNl4.js (new) 3.73 kB 🔴 +3.73 kB 🔴 +1.54 kB 🔴 +1.36 kB
assets/FormSearchInput-y8BSEeZq.js (removed) 3.73 kB 🟢 -3.73 kB 🟢 -1.55 kB 🟢 -1.36 kB
assets/Button-CrKOFAUC.js (new) 3.08 kB 🔴 +3.08 kB 🔴 +1.24 kB 🔴 +1.1 kB
assets/Button-D-9gIyaN.js (removed) 3.08 kB 🟢 -3.08 kB 🟢 -1.25 kB 🟢 -1.1 kB
assets/SubscribeButton-B3Fsx1R5.js (new) 2.34 kB 🔴 +2.34 kB 🔴 +1.01 kB 🔴 +881 B
assets/SubscribeButton-Cib28qDn.js (removed) 2.34 kB 🟢 -2.34 kB 🟢 -1.01 kB 🟢 -882 B
assets/WidgetButton-DGB_HZfA.js (removed) 1.84 kB 🟢 -1.84 kB 🟢 -876 B 🟢 -765 B
assets/WidgetButton-Fs0SmP9D.js (new) 1.84 kB 🔴 +1.84 kB 🔴 +875 B 🔴 +766 B
assets/cloudFeedbackTopbarButton-BY-EjvLt.js (new) 1.64 kB 🔴 +1.64 kB 🔴 +871 B 🔴 +771 B
assets/cloudFeedbackTopbarButton-CLC_xluf.js (removed) 1.64 kB 🟢 -1.64 kB 🟢 -874 B 🟢 -780 B
assets/CloudBadge-Cx_Hg1jw.js (removed) 1.11 kB 🟢 -1.11 kB 🟢 -568 B 🟢 -495 B
assets/CloudBadge-xw84SNGl.js (new) 1.11 kB 🔴 +1.11 kB 🔴 +570 B 🔴 +492 B
assets/ComfyQueueButton-B5Go5sD3.js (new) 836 B 🔴 +836 B 🔴 +411 B 🔴 +368 B
assets/ComfyQueueButton-DaA-ujhO.js (removed) 836 B 🟢 -836 B 🟢 -414 B 🟢 -368 B
assets/UserAvatar-BQCZ8iNl.js 1.17 kB 1.17 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 11 added / 11 removed

Data & Services — 2.74 MB (baseline 2.73 MB) • 🔴 +8.68 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-srqGXzLa.js (removed) 1.91 MB 🟢 -1.91 MB 🟢 -429 kB 🟢 -322 kB
assets/dialogService-CD1EFBOM.js (new) 1.91 MB 🔴 +1.91 MB 🔴 +429 kB 🔴 +322 kB
assets/api-BCOu_DpG.js (new) 699 kB 🔴 +699 kB 🔴 +158 kB 🔴 +125 kB
assets/api-DfYu__Gh.js (removed) 689 kB 🟢 -689 kB 🟢 -155 kB 🟢 -124 kB
assets/load3dService-Bxd8TwEe.js (new) 91 kB 🔴 +91 kB 🔴 +19.1 kB 🔴 +16.4 kB
assets/load3dService-CtgLnUoy.js (removed) 91 kB 🟢 -91 kB 🟢 -19.1 kB 🟢 -16.4 kB
assets/extensionStore-1j1dr3_-.js (new) 13 kB 🔴 +13 kB 🔴 +4.42 kB 🔴 +3.9 kB
assets/extensionStore-NoQ_y_9i.js (removed) 13 kB 🟢 -13 kB 🟢 -4.42 kB 🟢 -3.9 kB
assets/releaseStore-BU-qrWoa.js (new) 7.96 kB 🔴 +7.96 kB 🔴 +2.22 kB 🔴 +1.95 kB
assets/releaseStore-DswcQujh.js (removed) 7.96 kB 🟢 -7.96 kB 🟢 -2.22 kB 🟢 -1.95 kB
assets/keybindingService-BMhgWANw.js (removed) 6.52 kB 🟢 -6.52 kB 🟢 -1.71 kB 🟢 -1.47 kB
assets/keybindingService-Zj5RikUb.js (new) 6.52 kB 🔴 +6.52 kB 🔴 +1.71 kB 🔴 +1.48 kB
assets/bootstrapStore-B17ibfp9.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +871 B 🔴 +793 B
assets/bootstrapStore-Dv3R9HSQ.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -873 B 🟢 -791 B
assets/userStore-AhjtlaV0.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +718 B 🔴 +674 B
assets/userStore-D5cDfQ87.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -721 B 🟢 -679 B
assets/audioService-BYD1UXis.js (new) 1.73 kB 🔴 +1.73 kB 🔴 +849 B 🔴 +729 B
assets/audioService-gGYgqXct.js (removed) 1.73 kB 🟢 -1.73 kB 🟢 -850 B 🟢 -728 B
assets/releaseStore-BB4uzKhp.js (removed) 803 B 🟢 -803 B 🟢 -405 B 🟢 -356 B
assets/releaseStore-Bik1h5q7.js (new) 803 B 🔴 +803 B 🔴 +403 B 🔴 +355 B
assets/settingStore-CgHFiqYJ.js (removed) 787 B 🟢 -787 B 🟢 -407 B 🟢 -357 B
assets/settingStore-D3UYzm6l.js (new) 787 B 🔴 +787 B 🔴 +405 B 🔴 +357 B
assets/workflowDraftStore-BEd_aWr1.js (new) 779 B 🔴 +779 B 🔴 +398 B 🔴 +346 B
assets/workflowDraftStore-DVBkT7X9.js (removed) 779 B 🟢 -779 B 🟢 -400 B 🟢 -352 B
assets/dialogService-C553ybQ3.js (new) 768 B 🔴 +768 B 🔴 +388 B 🔴 +342 B
assets/dialogService-DkG0km7l.js (removed) 768 B 🟢 -768 B 🟢 -391 B 🟢 -343 B
assets/serverConfigStore-BJqmVNuT.js 2.32 kB 2.32 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 13 added / 13 removed

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

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useLoad3d-CJbe9m6G.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.63 kB 🟢 -3.21 kB
assets/useLoad3d-L1RJHKoM.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.63 kB 🔴 +3.21 kB
assets/useLoad3dViewer-D8wjEj9m.js (new) 14.1 kB 🔴 +14.1 kB 🔴 +3.15 kB 🔴 +2.8 kB
assets/useLoad3dViewer-DVBnIXRf.js (removed) 14.1 kB 🟢 -14.1 kB 🟢 -3.15 kB 🟢 -2.8 kB
assets/colorUtil-BJSyqxym.js (removed) 7 kB 🟢 -7 kB 🟢 -2.14 kB 🟢 -1.9 kB
assets/colorUtil-Bmtvl5il.js (new) 7 kB 🔴 +7 kB 🔴 +2.14 kB 🔴 +1.89 kB
assets/useFeatureFlags-BboWDzvb.js (removed) 4.14 kB 🟢 -4.14 kB 🟢 -1.23 kB 🟢 -1.06 kB
assets/useFeatureFlags-DL9Iqy65.js (new) 4.14 kB 🔴 +4.14 kB 🔴 +1.24 kB 🔴 +1.06 kB
assets/useWorkspaceUI-P9uDIwcQ.js (removed) 3 kB 🟢 -3 kB 🟢 -823 B 🟢 -703 B
assets/useWorkspaceUI-qRhHtmXZ.js (new) 3 kB 🔴 +3 kB 🔴 +823 B 🔴 +703 B
assets/subscriptionCheckoutUtil-ihceL7ty.js (removed) 2.53 kB 🟢 -2.53 kB 🟢 -1.05 kB 🟢 -953 B
assets/subscriptionCheckoutUtil-uCJJOwIi.js (new) 2.53 kB 🔴 +2.53 kB 🔴 +1.05 kB 🔴 +955 B
assets/useErrorHandling-CrQ7vozE.js (new) 1.5 kB 🔴 +1.5 kB 🔴 +629 B 🔴 +534 B
assets/useErrorHandling-DWS0UcQu.js (removed) 1.5 kB 🟢 -1.5 kB 🟢 -627 B 🟢 -532 B
assets/useWorkspaceSwitch-ClrobaIp.js (new) 1.25 kB 🔴 +1.25 kB 🔴 +543 B 🔴 +479 B
assets/useWorkspaceSwitch-CxJmoqXx.js (removed) 1.25 kB 🟢 -1.25 kB 🟢 -543 B 🟢 -487 B
assets/useLoad3d-CeN2Zssq.js (new) 902 B 🔴 +902 B 🔴 +442 B 🔴 +397 B
assets/useLoad3d-CiD7s9Le.js (removed) 902 B 🟢 -902 B 🟢 -445 B 🟢 -397 B
assets/useLoad3dViewer-B0dIdxJ_.js (removed) 881 B 🟢 -881 B 🟢 -431 B 🟢 -384 B
assets/useLoad3dViewer-BpU4ZvBb.js (new) 881 B 🔴 +881 B 🔴 +427 B 🔴 +384 B
assets/audioUtils-CbnBStK7.js (removed) 858 B 🟢 -858 B 🟢 -501 B 🟢 -411 B
assets/audioUtils-ZksbFlOO.js (new) 858 B 🔴 +858 B 🔴 +500 B 🔴 +402 B
assets/useCurrentUser-BnZuTNYn.js (removed) 765 B 🟢 -765 B 🟢 -394 B 🟢 -343 B
assets/useCurrentUser-cRt5m45-.js (new) 765 B 🔴 +765 B 🔴 +392 B 🔴 +342 B
assets/_plugin-vue_export-helper-ralzwvFM.js 315 B 315 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/envUtil-CC-l-L9z.js 466 B 466 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/markdownRendererUtil-Cddas8Zl.js 1.56 kB 1.56 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SkeletonUtils-CsnHjXS0.js 133 B 133 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useCopyToClipboard-BO59-OsU.js 944 B 944 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/useExternalLink-kHTV1WH8.js 1.66 kB 1.66 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 12 added / 12 removed

Vendor & Third-Party — 8.86 MB (baseline 8.86 MB) • 🟢 -12 B

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-other-BSv7ubBF.js (removed) 1.52 MB 🟢 -1.52 MB 🟢 -320 kB 🟢 -254 kB
assets/vendor-other-B77l219k.js (new) 1.52 MB 🔴 +1.52 MB 🔴 +320 kB 🔴 +254 kB
assets/vendor-axios-Cp6hch1I.js 70.7 kB 70.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-chart-l-KY-tZQ.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-DccD0mxo.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-primevue-D2gQvLCc.js 1.73 MB 1.73 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-reka-ui-C-tDe-KL.js 399 kB 399 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-ueviNA60.js 1.8 MB 1.8 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-tiptap-BnYkbQDM.js 634 kB 634 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vue-core-CmHHRvL9.js 311 kB 311 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vueuse-cYXYo6hi.js 121 kB 121 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-XVeHlCL4.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

Status: 1 added / 1 removed

Other — 7.92 MB (baseline 7.92 MB) • ⚪ 0 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/core-CaIZUe47.js (removed) 73.7 kB 🟢 -73.7 kB 🟢 -19 kB 🟢 -16.3 kB
assets/core-Knt-X2cz.js (new) 73.7 kB 🔴 +73.7 kB 🔴 +19 kB 🔴 +16.3 kB
assets/groupNode-BdAdEXm1.js (removed) 71.8 kB 🟢 -71.8 kB 🟢 -17.7 kB 🟢 -15.5 kB
assets/groupNode-KVZwSm1t.js (new) 71.8 kB 🔴 +71.8 kB 🔴 +17.7 kB 🔴 +15.6 kB
assets/WidgetSelect-BigJSXrY.js (removed) 58.3 kB 🟢 -58.3 kB 🟢 -12.5 kB 🟢 -10.7 kB
assets/WidgetSelect-Dt8IiPgc.js (new) 58.3 kB 🔴 +58.3 kB 🔴 +12.5 kB 🔴 +10.7 kB
assets/SubscriptionRequiredDialogContentWorkspace-BBQ1LAD9.js (removed) 46.4 kB 🟢 -46.4 kB 🟢 -8.69 kB 🟢 -7.51 kB
assets/SubscriptionRequiredDialogContentWorkspace-BZ5cbfUF.js (new) 46.4 kB 🔴 +46.4 kB 🔴 +8.69 kB 🔴 +7.52 kB
assets/WidgetPainter-BjIC8lT1.js (removed) 32.9 kB 🟢 -32.9 kB 🟢 -7.98 kB 🟢 -7.07 kB
assets/WidgetPainter-CzhWgjeX.js (new) 32.9 kB 🔴 +32.9 kB 🔴 +7.98 kB 🔴 +7.09 kB
assets/Load3DControls-C5Y7akkm.js (removed) 30.9 kB 🟢 -30.9 kB 🟢 -5.34 kB 🟢 -4.65 kB
assets/Load3DControls-CkQJ-trP.js (new) 30.9 kB 🔴 +30.9 kB 🔴 +5.34 kB 🔴 +4.65 kB
assets/WorkspacePanelContent-iSiJtxc9.js (removed) 29.3 kB 🟢 -29.3 kB 🟢 -6.17 kB 🟢 -5.4 kB
assets/WorkspacePanelContent-SmePcSHx.js (new) 29.3 kB 🔴 +29.3 kB 🔴 +6.17 kB 🔴 +5.42 kB
assets/SubscriptionRequiredDialogContent-Bekw-ZEu.js (new) 25.7 kB 🔴 +25.7 kB 🔴 +6.6 kB 🔴 +5.8 kB
assets/SubscriptionRequiredDialogContent-BVMohUTi.js (removed) 25.7 kB 🟢 -25.7 kB 🟢 -6.59 kB 🟢 -5.8 kB
assets/Load3dViewerContent-CD5ojptb.js (removed) 23 kB 🟢 -23 kB 🟢 -5.18 kB 🟢 -4.49 kB
assets/Load3dViewerContent-CvnCl-AT.js (new) 23 kB 🔴 +23 kB 🔴 +5.18 kB 🔴 +4.49 kB
assets/WidgetImageCrop-D06ESmXO.js (new) 22.2 kB 🔴 +22.2 kB 🔴 +5.53 kB 🔴 +4.87 kB
assets/WidgetImageCrop-KDNjB7l9.js (removed) 22.2 kB 🟢 -22.2 kB 🟢 -5.53 kB 🟢 -4.87 kB
assets/SubscriptionPanelContentWorkspace-BUuYDXie.js (new) 22 kB 🔴 +22 kB 🔴 +5.11 kB 🔴 +4.5 kB
assets/SubscriptionPanelContentWorkspace-C7HghRTV.js (removed) 22 kB 🟢 -22 kB 🟢 -5.11 kB 🟢 -4.51 kB
assets/CurrentUserPopoverWorkspace-D8aGmiaX.js (new) 20.5 kB 🔴 +20.5 kB 🔴 +4.96 kB 🔴 +4.43 kB
assets/CurrentUserPopoverWorkspace-DwNX2qS-.js (removed) 20.5 kB 🟢 -20.5 kB 🟢 -4.96 kB 🟢 -4.43 kB
assets/SignInContent-b9yM6dhz.js (removed) 18.9 kB 🟢 -18.9 kB 🟢 -4.79 kB 🟢 -4.18 kB
assets/SignInContent-Be_6F_wl.js (new) 18.9 kB 🔴 +18.9 kB 🔴 +4.78 kB 🔴 +4.19 kB
assets/WidgetInputNumber-Bw8ECvRp.js (removed) 18.8 kB 🟢 -18.8 kB 🟢 -4.8 kB 🟢 -4.27 kB
assets/WidgetInputNumber-C2KuWMMb.js (new) 18.8 kB 🔴 +18.8 kB 🔴 +4.8 kB 🔴 +4.26 kB
assets/WidgetRecordAudio-B-F_uZMf.js (removed) 17.4 kB 🟢 -17.4 kB 🟢 -5.02 kB 🟢 -4.47 kB
assets/WidgetRecordAudio-CoKDeikH.js (new) 17.4 kB 🔴 +17.4 kB 🔴 +5.01 kB 🔴 +4.49 kB
assets/Load3D-BXJZq_wW.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -4.03 kB 🟢 -3.52 kB
assets/Load3D-Cn6UnpaK.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +4.02 kB 🔴 +3.51 kB
assets/load3d-Bh1btxpc.js (removed) 14.8 kB 🟢 -14.8 kB 🟢 -4.21 kB 🟢 -3.65 kB
assets/load3d-Bkgygzhq.js (new) 14.8 kB 🔴 +14.8 kB 🔴 +4.21 kB 🔴 +3.64 kB
assets/AudioPreviewPlayer-BjtidZ6A.js (removed) 11 kB 🟢 -11 kB 🟢 -3.26 kB 🟢 -2.92 kB
assets/AudioPreviewPlayer-FD9EVf-v.js (new) 11 kB 🔴 +11 kB 🔴 +3.26 kB 🔴 +2.91 kB
assets/changeTracker-ayBehz0e.js (new) 9.43 kB 🔴 +9.43 kB 🔴 +2.9 kB 🔴 +2.56 kB
assets/changeTracker-Vh9x5PxT.js (removed) 9.43 kB 🟢 -9.43 kB 🟢 -2.9 kB 🟢 -2.55 kB
assets/nodeTemplates-DOXa-vJ6.js (removed) 9.34 kB 🟢 -9.34 kB 🟢 -3.27 kB 🟢 -2.87 kB
assets/nodeTemplates-JnV1idpQ.js (new) 9.34 kB 🔴 +9.34 kB 🔴 +3.27 kB 🔴 +2.88 kB
assets/SelectValue-CedvgC2B.js (removed) 8.94 kB 🟢 -8.94 kB 🟢 -2.27 kB 🟢 -1.99 kB
assets/SelectValue-DM5O8aQe.js (new) 8.94 kB 🔴 +8.94 kB 🔴 +2.27 kB 🔴 +2 kB
assets/InviteMemberDialogContent-CyXXcaws.js (new) 7.43 kB 🔴 +7.43 kB 🔴 +2.31 kB 🔴 +2.02 kB
assets/InviteMemberDialogContent-KWiDpjxj.js (removed) 7.43 kB 🟢 -7.43 kB 🟢 -2.31 kB 🟢 -2.02 kB
assets/WidgetImageCompare-D8KsOXvZ.js (new) 7 kB 🔴 +7 kB 🔴 +2.07 kB 🔴 +1.81 kB
assets/WidgetImageCompare-HFijaR80.js (removed) 7 kB 🟢 -7 kB 🟢 -2.07 kB 🟢 -1.8 kB
assets/WidgetToggleSwitch-9dIESGMI.js (removed) 6.8 kB 🟢 -6.8 kB 🟢 -2.19 kB 🟢 -1.94 kB
assets/WidgetToggleSwitch-DZcKOrD3.js (new) 6.8 kB 🔴 +6.8 kB 🔴 +2.19 kB 🔴 +1.94 kB
assets/Load3DConfiguration-CYCsV9cF.js (new) 6.27 kB 🔴 +6.27 kB 🔴 +1.91 kB 🔴 +1.68 kB
assets/Load3DConfiguration-DeatXbwt.js (removed) 6.27 kB 🟢 -6.27 kB 🟢 -1.91 kB 🟢 -1.68 kB
assets/CreateWorkspaceDialogContent-BVkCwoCN.js (removed) 5.57 kB 🟢 -5.57 kB 🟢 -2.01 kB 🟢 -1.75 kB
assets/CreateWorkspaceDialogContent-CnabLz-U.js (new) 5.57 kB 🔴 +5.57 kB 🔴 +2.01 kB 🔴 +1.76 kB
assets/onboardingCloudRoutes-DoU7BEQG.js (removed) 5.47 kB 🟢 -5.47 kB 🟢 -1.86 kB 🟢 -1.62 kB
assets/onboardingCloudRoutes-mlrqXZQ9.js (new) 5.47 kB 🔴 +5.47 kB 🔴 +1.85 kB 🔴 +1.61 kB
assets/FreeTierDialogContent-Cl3k3LrB.js (new) 5.43 kB 🔴 +5.43 kB 🔴 +1.92 kB 🔴 +1.69 kB
assets/FreeTierDialogContent-tfKPz_Og.js (removed) 5.43 kB 🟢 -5.43 kB 🟢 -1.92 kB 🟢 -1.69 kB
assets/EditWorkspaceDialogContent-CdMdmYsU.js (new) 5.37 kB 🔴 +5.37 kB 🔴 +1.96 kB 🔴 +1.72 kB
assets/EditWorkspaceDialogContent-DF0YQnRZ.js (removed) 5.37 kB 🟢 -5.37 kB 🟢 -1.97 kB 🟢 -1.72 kB
assets/WidgetTextarea-B_hjlVQ4.js (new) 5.07 kB 🔴 +5.07 kB 🔴 +1.84 kB 🔴 +1.63 kB
assets/WidgetTextarea-CXXorPof.js (removed) 5.07 kB 🟢 -5.07 kB 🟢 -1.84 kB 🟢 -1.63 kB
assets/ValueControlPopover-BtzStfbC.js (removed) 4.96 kB 🟢 -4.96 kB 🟢 -1.79 kB 🟢 -1.6 kB
assets/ValueControlPopover-OpkQPzzo.js (new) 4.96 kB 🔴 +4.96 kB 🔴 +1.79 kB 🔴 +1.6 kB
assets/Preview3d-CpADbQ2b.js (removed) 4.89 kB 🟢 -4.89 kB 🟢 -1.62 kB 🟢 -1.41 kB
assets/Preview3d-I29x5-8p.js (new) 4.89 kB 🔴 +4.89 kB 🔴 +1.61 kB 🔴 +1.4 kB
assets/CancelSubscriptionDialogContent-UIBo-Uo2.js (new) 4.83 kB 🔴 +4.83 kB 🔴 +1.8 kB 🔴 +1.58 kB
assets/CancelSubscriptionDialogContent-zzkhSPg6.js (removed) 4.83 kB 🟢 -4.83 kB 🟢 -1.8 kB 🟢 -1.58 kB
assets/AnimationControls-Bal5YgT5.js (removed) 4.61 kB 🟢 -4.61 kB 🟢 -1.6 kB 🟢 -1.42 kB
assets/AnimationControls-CkQCGosO.js (new) 4.61 kB 🔴 +4.61 kB 🔴 +1.6 kB 🔴 +1.41 kB
assets/DeleteWorkspaceDialogContent-BWIxqyly.js (removed) 4.27 kB 🟢 -4.27 kB 🟢 -1.65 kB 🟢 -1.44 kB
assets/DeleteWorkspaceDialogContent-CruGOLLF.js (new) 4.27 kB 🔴 +4.27 kB 🔴 +1.65 kB 🔴 +1.44 kB
assets/WidgetWithControl-BCmQlF5d.js (removed) 4.14 kB 🟢 -4.14 kB 🟢 -1.8 kB 🟢 -1.63 kB
assets/WidgetWithControl-ZRRzUyfN.js (new) 4.14 kB 🔴 +4.14 kB 🔴 +1.8 kB 🔴 +1.61 kB
assets/LeaveWorkspaceDialogContent-bNDuGbUn.js (new) 4.1 kB 🔴 +4.1 kB 🔴 +1.6 kB 🔴 +1.39 kB
assets/LeaveWorkspaceDialogContent-oeNmwCn7.js (removed) 4.1 kB 🟢 -4.1 kB 🟢 -1.6 kB 🟢 -1.39 kB
assets/RemoveMemberDialogContent-Bi_VKlFT.js (new) 4.08 kB 🔴 +4.08 kB 🔴 +1.55 kB 🔴 +1.35 kB
assets/RemoveMemberDialogContent-Bzf1K5_o.js (removed) 4.08 kB 🟢 -4.08 kB 🟢 -1.55 kB 🟢 -1.35 kB
assets/RevokeInviteDialogContent-CFwwsck6.js (removed) 4 kB 🟢 -4 kB 🟢 -1.56 kB 🟢 -1.37 kB
assets/RevokeInviteDialogContent-DS1NBORz.js (new) 4 kB 🔴 +4 kB 🔴 +1.56 kB 🔴 +1.37 kB
assets/InviteMemberUpsellDialogContent-BYEV0vYG.js (removed) 3.87 kB 🟢 -3.87 kB 🟢 -1.43 kB 🟢 -1.25 kB
assets/InviteMemberUpsellDialogContent-CLD2q93k.js (new) 3.87 kB 🔴 +3.87 kB 🔴 +1.42 kB 🔴 +1.25 kB
assets/tierBenefits-BdmSMnoB.js (removed) 3.66 kB 🟢 -3.66 kB 🟢 -1.31 kB 🟢 -1.18 kB
assets/tierBenefits-Bq01VlJC.js (new) 3.66 kB 🔴 +3.66 kB 🔴 +1.31 kB 🔴 +1.18 kB
assets/Popover-4Zg39Mho.js (new) 3.65 kB 🔴 +3.65 kB 🔴 +1.44 kB 🔴 +1.27 kB
assets/Popover-iM3e9lB4.js (removed) 3.65 kB 🟢 -3.65 kB 🟢 -1.44 kB 🟢 -1.27 kB
assets/Slider-D4A5BM3I.js (removed) 3.52 kB 🟢 -3.52 kB 🟢 -1.36 kB 🟢 -1.19 kB
assets/Slider-DrW0iyjG.js (new) 3.52 kB 🔴 +3.52 kB 🔴 +1.36 kB 🔴 +1.19 kB
assets/saveMesh-B_boqhny.js (new) 3.42 kB 🔴 +3.42 kB 🔴 +1.47 kB 🔴 +1.32 kB
assets/saveMesh-Bhe-Tppd.js (removed) 3.42 kB 🟢 -3.42 kB 🟢 -1.47 kB 🟢 -1.32 kB
assets/WidgetBoundingBox-tncBW5.js (new) 3.19 kB 🔴 +3.19 kB 🔴 +894 B 🔴 +780 B
assets/WidgetBoundingBox-Dx4DYlRd.js (removed) 3.19 kB 🟢 -3.19 kB 🟢 -894 B 🟢 -780 B
assets/cloudSessionCookie-CYdN0AO7.js (new) 3.14 kB 🔴 +3.14 kB 🔴 +1.1 kB 🔴 +957 B
assets/cloudSessionCookie-uS6meweV.js (removed) 3.14 kB 🟢 -3.14 kB 🟢 -1.11 kB 🟢 -992 B
assets/GlobalToast-CPE7ANQ2.js (new) 2.91 kB 🔴 +2.91 kB 🔴 +1.21 kB 🔴 +1.04 kB
assets/GlobalToast-CuhCjP_w.js (removed) 2.91 kB 🟢 -2.91 kB 🟢 -1.21 kB 🟢 -1.03 kB
assets/WidgetColorPicker-Bwz_j03q.js (new) 2.9 kB 🔴 +2.9 kB 🔴 +1.23 kB 🔴 +1.1 kB
assets/WidgetColorPicker-C4yd_6Zg.js (removed) 2.9 kB 🟢 -2.9 kB 🟢 -1.23 kB 🟢 -1.11 kB
assets/MediaVideoTop-Bt9JrPXr.js (new) 2.77 kB 🔴 +2.77 kB 🔴 +1.13 kB 🔴 +1 kB
assets/MediaVideoTop-WILUyTDj.js (removed) 2.77 kB 🟢 -2.77 kB 🟢 -1.13 kB 🟢 -995 B
assets/ApiNodesSignInContent-BRnxPwkH.js (removed) 2.69 kB 🟢 -2.69 kB 🟢 -1.05 kB 🟢 -963 B
assets/ApiNodesSignInContent-nnQZ5amC.js (new) 2.69 kB 🔴 +2.69 kB 🔴 +1.05 kB 🔴 +921 B
assets/SubscribeToRun--TpRpDtr.js (removed) 2.06 kB 🟢 -2.06 kB 🟢 -948 B 🟢 -842 B
assets/SubscribeToRun-6TRocj-F.js (new) 2.06 kB 🔴 +2.06 kB 🔴 +950 B 🔴 +847 B
assets/WidgetLayoutField-D9bU0x2o.js (new) 1.98 kB 🔴 +1.98 kB 🔴 +891 B 🔴 +782 B
assets/WidgetLayoutField-S6xnM-i1.js (removed) 1.98 kB 🟢 -1.98 kB 🟢 -892 B 🟢 -793 B
assets/WidgetInputText-CFc2rQ4D.js (new) 1.86 kB 🔴 +1.86 kB 🔴 +875 B 🔴 +798 B
assets/WidgetInputText-W9vcgWMp.js (removed) 1.86 kB 🟢 -1.86 kB 🟢 -877 B 🟢 -796 B
assets/CloudRunButtonWrapper-BbxcK23y.js (new) 1.72 kB 🔴 +1.72 kB 🔴 +807 B 🔴 +713 B
assets/CloudRunButtonWrapper-BToQsHQy.js (removed) 1.72 kB 🟢 -1.72 kB 🟢 -804 B 🟢 -733 B
assets/cloudBadges-CqqI-PFF.js (removed) 1.41 kB 🟢 -1.41 kB 🟢 -725 B 🟢 -633 B
assets/cloudBadges-D7GOrb73.js (new) 1.41 kB 🔴 +1.41 kB 🔴 +723 B 🔴 +632 B
assets/previousFullPath-BJgxK1xC.js (new) 1.39 kB 🔴 +1.39 kB 🔴 +653 B 🔴 +575 B
assets/previousFullPath-DrlHPlNV.js (removed) 1.39 kB 🟢 -1.39 kB 🟢 -649 B 🟢 -576 B
assets/cloudSubscription-D5m02FHT.js (removed) 1.37 kB 🟢 -1.37 kB 🟢 -677 B 🟢 -584 B
assets/cloudSubscription-Dih-L5ct.js (new) 1.37 kB 🔴 +1.37 kB 🔴 +675 B 🔴 +583 B
assets/VideoPlayOverlay-BYgmR44k.js (new) 1.35 kB 🔴 +1.35 kB 🔴 +702 B 🔴 +618 B
assets/VideoPlayOverlay-d1kDZ1Hw.js (removed) 1.35 kB 🟢 -1.35 kB 🟢 -703 B 🟢 -622 B
assets/Load3D-B51BRdvX.js (removed) 1.11 kB 🟢 -1.11 kB 🟢 -518 B 🟢 -456 B
assets/Load3D-DWXs1R2u.js (new) 1.11 kB 🔴 +1.11 kB 🔴 +510 B 🔴 +454 B
assets/nightlyBadges-BLawIqVc.js (removed) 1.04 kB 🟢 -1.04 kB 🟢 -552 B 🟢 -488 B
assets/nightlyBadges-ykfW8SH3.js (new) 1.04 kB 🔴 +1.04 kB 🔴 +549 B 🔴 +489 B
assets/Load3dViewerContent-CatGMQUL.js (removed) 1.04 kB 🟢 -1.04 kB 🟢 -486 B 🟢 -429 B
assets/Load3dViewerContent-nhWYXDtQ.js (new) 1.04 kB 🔴 +1.04 kB 🔴 +483 B 🔴 +430 B
assets/SubscriptionPanelContentWorkspace-ClaiNsYa.js (removed) 963 B 🟢 -963 B 🟢 -458 B 🟢 -397 B
assets/SubscriptionPanelContentWorkspace-T6zU5Tg6.js (new) 963 B 🔴 +963 B 🔴 +457 B 🔴 +390 B
assets/changeTracker-DcUdOHdA.js (new) 800 B 🔴 +800 B 🔴 +402 B 🔴 +350 B
assets/changeTracker-TwFK65mh.js (removed) 800 B 🟢 -800 B 🟢 -406 B 🟢 -351 B
assets/WidgetLegacy-BQv4BcqI.js (removed) 787 B 🟢 -787 B 🟢 -404 B 🟢 -348 B
assets/WidgetLegacy-D-yqxycb.js (new) 787 B 🔴 +787 B 🔴 +401 B 🔴 +347 B
assets/graphHasMissingNodes-CQ7YSklr.js (new) 761 B 🔴 +761 B 🔴 +375 B 🔴 +323 B
assets/graphHasMissingNodes-DQ1VKGVJ.js (removed) 761 B 🟢 -761 B 🟢 -375 B 🟢 -324 B
assets/WidgetBoundingBox-dQroUNAm.js (new) 283 B 🔴 +283 B 🔴 +184 B 🔴 +167 B
assets/WidgetBoundingBox-ThP4bP1R.js (removed) 283 B 🟢 -283 B 🟢 -183 B 🟢 -162 B
assets/src-C1_d8pHn.js (new) 251 B 🔴 +251 B 🔴 +211 B 🔴 +194 B
assets/src-KRN59wM5.js (removed) 251 B 🟢 -251 B 🟢 -214 B 🟢 -194 B
assets/auto-Bt3L7FBS.js 1.7 kB 1.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/BaseViewTemplate-BU8wYnsH.js 1.78 kB 1.78 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/comfy-logo-single-CoSPG7JX.js 198 B 198 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/ComfyOrgHeader-CXepDPJT.js 910 B 910 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-32T5PqF_.js 19 kB 19 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Ak-xav7X.js 17.7 kB 17.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-B7yDR3IF.js 16.2 kB 16.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BfgrmbF3.js 16.3 kB 16.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BKiowuzF.js 17.1 kB 17.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-C2ScMdCP.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CIaXr_da.js 16.3 kB 16.3 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CQ_1hcIQ.js 15.2 kB 15.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-D9CRAR5X.js 17.7 kB 17.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DgrxZ384.js 16.9 kB 16.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-Dm6gdG1p.js 15.4 kB 15.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/constants-CobilG8q.js 579 B 579 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-CgFxmlcu.js 535 kB 535 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/i18n-eWU18WjT.js 199 B 199 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-a_fD2A1w.js 151 kB 151 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Bi1XfdpF.js 188 kB 188 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BJQ0M9Yj.js 210 kB 210 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BvVDOLZZ.js 150 kB 150 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BZLcIBqp.js 173 kB 173 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CjbGas6Z.js 132 kB 132 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CZaOv97H.js 133 kB 133 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-D6nxp_Jl.js 153 kB 153 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-dKpvLPCH.js 181 kB 181 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-dXT40Jsi.js 158 kB 158 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-WPbnhfZ5.js 155 kB 155 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Media3DTop-CZ4wk4U1.js 1.82 kB 1.82 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaAudioTop-pqbTSt64.js 1.43 kB 1.43 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaImageTop-CfDqPsGU.js 1.75 kB 1.75 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaOtherTop-B84VMc_d.js 1.02 kB 1.02 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaTextTop-DIqLHunN.js 1.01 kB 1.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-46r0KRED.js 451 kB 451 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BrUpuJ60.js 396 kB 396 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C5Naq7UG.js 364 kB 364 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CenyYNO8.js 450 kB 450 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CN3CzRXx.js 404 kB 404 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Cz73oY7a.js 368 kB 368 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-D22mZTDT.js 400 kB 400 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-Dh2PHfFx.js 391 kB 391 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DJ0GsBGj.js 399 kB 399 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DJKCo7UK.js 416 kB 416 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-FjSUW2rw.js 491 kB 491 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 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/signInSchema-CUgdREzQ.js 1.53 kB 1.53 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/SubscriptionBenefits-BxBbqnOZ.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/types-DT3N7am7.js 204 B 204 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widget-DIRwAHBY.js 3.01 kB 3.01 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetChart-YZCBKjIT.js 2.21 kB 2.21 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetCurve-C3tuuOR3.js 9.36 kB 9.36 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetGalleria-tNR6Ubxu.js 3.61 kB 3.61 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/WidgetMarkdown-BqPoJLFX.js 2.93 kB 2.93 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-C6ZYch5M.js 1.1 kB 1.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetTypes-DYLaC2lj.js 393 B 393 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 67 added / 67 removed

@github-actions
Copy link

github-actions bot commented Feb 26, 2026

⚡ Performance Report

No regressions detected.

All metrics
Metric Baseline PR (n=3) Δ Sig
canvas-idle: style recalcs 124 124 +0% z=0.6
canvas-idle: layouts 0 0 +0% variance too high
canvas-idle: task duration 484ms 432ms -11% z=-0.0
canvas-mouse-sweep: style recalcs 188 175 -7% z=-0.1
canvas-mouse-sweep: layouts 12 12 -3% z=-0.7
canvas-mouse-sweep: task duration 1101ms 937ms -15% z=0.0
dom-widget-clipping: style recalcs 42 42 +1% z=0.0
dom-widget-clipping: layouts 0 0 +0% variance too high
dom-widget-clipping: task duration 365ms 352ms -4% z=-0.6
subgraph-dom-widget-clipping: style recalcs 75 75 +0% z=1.3
subgraph-dom-widget-clipping: layouts 0 0 variance too high
subgraph-dom-widget-clipping: task duration 443ms 434ms -2% z=0.7
subgraph-idle: style recalcs 121 122 +1% z=1.3
subgraph-idle: layouts 0 0 variance too high
subgraph-idle: task duration 426ms 403ms -5% z=-0.0
subgraph-mouse-sweep: style recalcs 155 168 +8% z=0.9
subgraph-mouse-sweep: layouts 16 16 +0%
subgraph-mouse-sweep: task duration 777ms 933ms +20% z=1.0
Historical variance (last 5 runs)
Metric μ σ CV
canvas-idle: style recalcs 124 1 0.6%
canvas-idle: layouts 0 0 91.3%
canvas-idle: task duration 433ms 50ms 11.5%
canvas-mouse-sweep: style recalcs 176 8 4.6%
canvas-mouse-sweep: layouts 12 0 1.5%
canvas-mouse-sweep: task duration 934ms 112ms 12.0%
dom-widget-clipping: style recalcs 42 1 3.2%
dom-widget-clipping: layouts 0 0 223.6%
dom-widget-clipping: task duration 364ms 18ms 4.9%
subgraph-dom-widget-clipping: style recalcs 74 1 1.2%
subgraph-dom-widget-clipping: layouts 0 0 223.6%
subgraph-dom-widget-clipping: task duration 423ms 14ms 3.4%
subgraph-idle: style recalcs 121 1 0.5%
subgraph-idle: layouts 0 0 149.1%
subgraph-idle: task duration 404ms 25ms 6.1%
subgraph-mouse-sweep: style recalcs 161 7 4.5%
subgraph-mouse-sweep: layouts 16 0 0.0%
subgraph-mouse-sweep: task duration 835ms 94ms 11.3%
Raw data
{
  "timestamp": "2026-03-05T02:40:44.568Z",
  "gitSha": "4d102ade8fe741fb7fe52f6fb1bd80991931a84b",
  "branch": "drjkl/he-come-to-town",
  "measurements": [
    {
      "name": "canvas-idle",
      "durationMs": 2030.7390000000396,
      "styleRecalcs": 124,
      "styleRecalcDurationMs": 25.776999999999997,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 430.58700000000005,
      "heapDeltaBytes": -1484924
    },
    {
      "name": "canvas-idle",
      "durationMs": 2024.39099999998,
      "styleRecalcs": 121,
      "styleRecalcDurationMs": 20.304,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 415.5639999999999,
      "heapDeltaBytes": -1678768
    },
    {
      "name": "canvas-idle",
      "durationMs": 2075.220999999999,
      "styleRecalcs": 128,
      "styleRecalcDurationMs": 29.883999999999997,
      "layouts": 1,
      "layoutDurationMs": 0.27599999999999997,
      "taskDurationMs": 450.918,
      "heapDeltaBytes": -1229416
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 2125.7699999999886,
      "styleRecalcs": 191,
      "styleRecalcDurationMs": 63.238,
      "layouts": 12,
      "layoutDurationMs": 3.81,
      "taskDurationMs": 1180.2440000000001,
      "heapDeltaBytes": -1649400
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1834.1500000000224,
      "styleRecalcs": 166,
      "styleRecalcDurationMs": 50.956,
      "layouts": 12,
      "layoutDurationMs": 3.306,
      "taskDurationMs": 811.5969999999999,
      "heapDeltaBytes": -3926836
    },
    {
      "name": "canvas-mouse-sweep",
      "durationMs": 1841.7239999999993,
      "styleRecalcs": 168,
      "styleRecalcDurationMs": 49.93300000000001,
      "layouts": 12,
      "layoutDurationMs": 3.1119999999999997,
      "taskDurationMs": 818.1709999999999,
      "heapDeltaBytes": -3596604
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 582.1189999999774,
      "styleRecalcs": 43,
      "styleRecalcDurationMs": 13.069,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 350.99,
      "heapDeltaBytes": 6709460
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 550.0049999999987,
      "styleRecalcs": 41,
      "styleRecalcDurationMs": 13.603,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 345.729,
      "heapDeltaBytes": 6253184
    },
    {
      "name": "dom-widget-clipping",
      "durationMs": 582.4649999999565,
      "styleRecalcs": 42,
      "styleRecalcDurationMs": 13.781999999999998,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 360.39500000000004,
      "heapDeltaBytes": 7872124
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 616.1430000000223,
      "styleRecalcs": 77,
      "styleRecalcDurationMs": 19.238,
      "layouts": 1,
      "layoutDurationMs": 0.174,
      "taskDurationMs": 436.824,
      "heapDeltaBytes": -7025736
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 624.7599999999807,
      "styleRecalcs": 75,
      "styleRecalcDurationMs": 16.041,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 427.74700000000007,
      "heapDeltaBytes": 14874456
    },
    {
      "name": "subgraph-dom-widget-clipping",
      "durationMs": 589.6789999999896,
      "styleRecalcs": 73,
      "styleRecalcDurationMs": 17.043,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 436.81199999999995,
      "heapDeltaBytes": -7723868
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2004.0099999999939,
      "styleRecalcs": 121,
      "styleRecalcDurationMs": 23.595000000000006,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 412.399,
      "heapDeltaBytes": -1843336
    },
    {
      "name": "subgraph-idle",
      "durationMs": 1993.7930000000392,
      "styleRecalcs": 120,
      "styleRecalcDurationMs": 19.720000000000002,
      "layouts": 0,
      "layoutDurationMs": 0,
      "taskDurationMs": 382.28000000000003,
      "heapDeltaBytes": -1846388
    },
    {
      "name": "subgraph-idle",
      "durationMs": 2031.8870000000402,
      "styleRecalcs": 125,
      "styleRecalcDurationMs": 25.794999999999998,
      "layouts": 1,
      "layoutDurationMs": 0.20699999999999993,
      "taskDurationMs": 414.15500000000003,
      "heapDeltaBytes": -1407688
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1776.8949999999677,
      "styleRecalcs": 159,
      "styleRecalcDurationMs": 50.136,
      "layouts": 16,
      "layoutDurationMs": 4.357,
      "taskDurationMs": 767.7969999999999,
      "heapDeltaBytes": -5192208
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 2007.2460000000092,
      "styleRecalcs": 173,
      "styleRecalcDurationMs": 62.847000000000016,
      "layouts": 16,
      "layoutDurationMs": 4.229,
      "taskDurationMs": 1009.849,
      "heapDeltaBytes": -3824472
    },
    {
      "name": "subgraph-mouse-sweep",
      "durationMs": 1967.0219999999858,
      "styleRecalcs": 171,
      "styleRecalcDurationMs": 59.21,
      "layouts": 16,
      "layoutDurationMs": 4.923,
      "taskDurationMs": 1022.5889999999999,
      "heapDeltaBytes": -4081232
    }
  ]
}

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

Caution

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

⚠️ Outside diff range comments (1)
src/lib/litegraph/src/subgraph/SubgraphOutput.ts (1)

54-68: ⚠️ Potential issue | 🟠 Major

Emit disconnect lifecycle event when replacing an existing subgraph-output link.

Line 62 removes the previous link but does not dispatch a connected: false node-connection change for the old output side, so disconnect callbacks can be skipped.

🔧 Proposed fix
     if (existingLink != null) {
       subgraph.beforeChange()

       const { outputNode } = existingLink.resolve(subgraph)
       if (!outputNode) throw new Error('Expected output node for existing link')
+      const previousOutput = outputNode.outputs[existingLink.origin_slot]

       subgraph.disconnectSubgraphOutputLink(
         this,
         outputNode,
         existingLink.origin_slot,
         existingLink
       )
+
+      if (previousOutput) {
+        graphLifecycleEventDispatcher.dispatchNodeConnectionChange({
+          node: outputNode,
+          slotType: NodeSlotType.OUTPUT,
+          slotIndex: existingLink.origin_slot,
+          connected: false,
+          link: existingLink,
+          slot: previousOutput
+        })
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/litegraph/src/subgraph/SubgraphOutput.ts` around lines 54 - 68,
Existing code disconnects the old link but does not notify the graph that the
output node lost a connection; after calling
subgraph.disconnectSubgraphOutputLink(this, outputNode,
existingLink.origin_slot, existingLink) emit the same node-connection change
used elsewhere to indicate the output side was disconnected (i.e., dispatch a
"connected: false" change for the old output node). Locate the helper/event used
for node connection changes in this codebase (for example any method named
nodeConnectionChanged, dispatchNodeConnectionChange, emitConnectionChange, or
similar) and call it with the outputNode, this output slot, and connected: false
immediately after disconnectSubgraphOutputLink (keeping the existing
subgraph.beforeChange() call).
🧹 Nitpick comments (1)
src/lib/litegraph/src/LGraphCanvas.ts (1)

8860-8865: Guard proxy-widget remap calls to mapped subgraph types.

Calling the adapter for every string-typed node relies on adapter no-op handling for undefined. An explicit guard makes caller-side behavior clearer and keeps the remap boundary more deterministic.

♻️ Suggested refactor
   for (const nodeInfo of allNodeInfo) {
     if (typeof nodeInfo.type !== 'string') continue
-    graphPersistenceAdapter.remapProxyWidgets(
-      nodeInfo,
-      subgraphNodeIdMap.get(nodeInfo.type)
-    )
+    const remappedIds = subgraphNodeIdMap.get(nodeInfo.type)
+    if (!remappedIds) continue
+    graphPersistenceAdapter.remapProxyWidgets(nodeInfo, remappedIds)
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/litegraph/src/LGraphCanvas.ts` around lines 8860 - 8865, The loop
calls graphPersistenceAdapter.remapProxyWidgets for any nodeInfo whose type is a
string, but passes subgraphNodeIdMap.get(nodeInfo.type) which may be undefined;
add an explicit guard so remapProxyWidgets is only invoked when
subgraphNodeIdMap.has(nodeInfo.type) (or the result of get(...) is
non-undefined). Update the block iterating allNodeInfo to check
subgraphNodeIdMap.has(nodeInfo.type) (or store const mapped =
subgraphNodeIdMap.get(nodeInfo.type) and check mapped !== undefined) before
calling graphPersistenceAdapter.remapProxyWidgets(nodeInfo, mapped) to make the
boundary deterministic.
🤖 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/lib/litegraph/src/LGraphNode.ts`:
- Around line 3236-3239: The disconnect dispatch is using the loop index into
output.links (i) instead of the actual source slot index, causing wrong-slot
notifications; update the call to
graphLifecycleEventDispatcher.dispatchDisconnectNodePair so sourceSlotIndex is
set to the origin slot index from the link object (e.g., use link.origin or
link.slot or link.origin_slotIndex depending on the link shape) rather than the
array index i, keeping sourceNode: target_node and sourceSlot: output unchanged;
locate the code around graphLifecycleEventDispatcher.dispatchDisconnectNodePair
and replace the i argument with the correct property from the link object
referenced when iterating output.links.

In `@src/lib/litegraph/src/subgraph/SubgraphInputNode.ts`:
- Around line 193-200: The dispatch payload on disconnect is using the wrong
slot object (slot: subgraphInput) for
graphLifecycleEventDispatcher.dispatchNodeConnectionChange with
NodeSlotType.INPUT; change the slot value to the actual target node input (e.g.,
node.inputs[slotIndex] or the node's getter such as node.getInputSlot(slotIndex)
if available) so the payload reflects the node's input slot, keeping slotIndex
and link unchanged.

In `@src/lib/litegraph/src/subgraph/SubgraphIO.test.ts`:
- Line 10: The import line for LinkDirection has a typo with a double slash in
the module path; update the import that references LinkDirection (the statement
importing from '@/lib/litegraph/src//types/globalEnums') to remove the extra
slash so it imports from '@/lib/litegraph/src/types/globalEnums'.

In `@src/scripts/app.test.ts`:
- Around line 161-173: Replace the unsafe casts: instead of "(app as
any).addAfterConfigureHandler(graph)" cast app to the narrow interface that
exposes addAfterConfigureHandler (e.g., an AppWithAfterConfigureHandler type)
and call addAfterConfigureHandler directly; also replace "graph.onConfigure?.({}
as never)" with a properly typed mock configuration value that matches the
expected parameter type of graph.onConfigure (or create a minimal stub object
typed to that config interface) so both type assertions are removed and type
safety is preserved for addAfterConfigureHandler, graph, and onConfigure.

---

Outside diff comments:
In `@src/lib/litegraph/src/subgraph/SubgraphOutput.ts`:
- Around line 54-68: Existing code disconnects the old link but does not notify
the graph that the output node lost a connection; after calling
subgraph.disconnectSubgraphOutputLink(this, outputNode,
existingLink.origin_slot, existingLink) emit the same node-connection change
used elsewhere to indicate the output side was disconnected (i.e., dispatch a
"connected: false" change for the old output node). Locate the helper/event used
for node connection changes in this codebase (for example any method named
nodeConnectionChanged, dispatchNodeConnectionChange, emitConnectionChange, or
similar) and call it with the outputNode, this output slot, and connected: false
immediately after disconnectSubgraphOutputLink (keeping the existing
subgraph.beforeChange() call).

---

Nitpick comments:
In `@src/lib/litegraph/src/LGraphCanvas.ts`:
- Around line 8860-8865: The loop calls
graphPersistenceAdapter.remapProxyWidgets for any nodeInfo whose type is a
string, but passes subgraphNodeIdMap.get(nodeInfo.type) which may be undefined;
add an explicit guard so remapProxyWidgets is only invoked when
subgraphNodeIdMap.has(nodeInfo.type) (or the result of get(...) is
non-undefined). Update the block iterating allNodeInfo to check
subgraphNodeIdMap.has(nodeInfo.type) (or store const mapped =
subgraphNodeIdMap.get(nodeInfo.type) and check mapped !== undefined) before
calling graphPersistenceAdapter.remapProxyWidgets(nodeInfo, mapped) to make the
boundary deterministic.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between aef299c and 025a803.

📒 Files selected for processing (25)
  • browser_tests/tests/changeTracker.spec.ts
  • src/lib/litegraph/src/LGraph.test.ts
  • src/lib/litegraph/src/LGraph.ts
  • src/lib/litegraph/src/LGraphCanvas.clipboard.test.ts
  • src/lib/litegraph/src/LGraphCanvas.ts
  • src/lib/litegraph/src/LGraphNode.test.ts
  • src/lib/litegraph/src/LGraphNode.ts
  • src/lib/litegraph/src/LLink.test.ts
  • src/lib/litegraph/src/LLink.ts
  • src/lib/litegraph/src/Reroute.ts
  • src/lib/litegraph/src/infrastructure/GraphLifecycleEventDispatcher.ts
  • src/lib/litegraph/src/infrastructure/GraphPersistenceAdapter.ts
  • src/lib/litegraph/src/infrastructure/SubgraphEventMap.ts
  • src/lib/litegraph/src/subgraph/SubgraphIO.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphInput.ts
  • src/lib/litegraph/src/subgraph/SubgraphInputNode.ts
  • src/lib/litegraph/src/subgraph/SubgraphNode.ts
  • src/lib/litegraph/src/subgraph/SubgraphOutput.ts
  • src/lib/litegraph/src/subgraph/subgraphUtils.ts
  • src/scripts/app.test.ts
  • src/scripts/app.ts
  • src/scripts/changeTracker.test.ts
  • src/stores/linkStore.ts
  • src/utils/litegraphUtil.test.ts
  • src/utils/litegraphUtil.ts

Comment on lines +161 to +173
;(app as any).addAfterConfigureHandler(graph)
graph.onConfigure?.({} as never)

expect(hasLegacyLinkInputSlotMismatch).toHaveBeenCalledWith(graph)
expect(fixLinkInputSlots).toHaveBeenCalledWith(graph)
})

it('skips legacy slot repair when no mismatch is present', () => {
vi.mocked(hasLegacyLinkInputSlotMismatch).mockReturnValue(false)
const graph = createConfigureGraph()

;(app as any).addAfterConfigureHandler(graph)
graph.onConfigure?.({} as never)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Read the test file around lines 161-173
sed -n '155,180p' src/scripts/app.test.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 952


🏁 Script executed:

#!/bin/bash
# Check the type of app variable and its definition in the test file
head -50 src/scripts/app.test.ts | grep -A5 -B5 'app\|import'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 698


🏁 Script executed:

#!/bin/bash
# Find where addAfterConfigureHandler is defined
rg 'addAfterConfigureHandler' --type ts -B2 -A2 | head -50

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1230


🏁 Script executed:

#!/bin/bash
# Check the full implementation of addAfterConfigureHandler to understand why it's private
sed -n '1,50p' src/scripts/app.ts | grep -A20 'class ComfyApp'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Look at the full addAfterConfigureHandler method implementation
rg -A10 'private addAfterConfigureHandler' src/scripts/app.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 458


🏁 Script executed:

#!/bin/bash
# Check if there are any comments in the test explaining the private method call
sed -n '140,180p' src/scripts/app.test.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1363


🏁 Script executed:

#!/bin/bash
# Check if Partial<ComfyApp> or a proper type is used elsewhere in tests for similar scenarios
rg 'Partial<' src/scripts/app.test.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 161


🏁 Script executed:

#!/bin/bash
# Check class definition and how addAfterConfigureHandler is used internally
rg 'class ComfyApp' src/scripts/app.ts -A5

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 178


🏁 Script executed:

#!/bin/bash
# Verify if addAfterConfigureHandler is called publicly elsewhere or if it's only internal
rg 'addAfterConfigureHandler' src/scripts/app.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 162


🏁 Script executed:

#!/bin/bash
# Check if there's a pattern for accessing private methods in other tests in the codebase
rg '\(.*as unknown as' src/**/*.test.ts -A1 | head -30

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 932


🏁 Script executed:

#!/bin/bash
# Check if there are other private method tests that use similar patterns
rg 'private.*graph.*LGraph' src/scripts/app.ts -B2 -A8

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 388


🏁 Script executed:

#!/bin/bash
# Verify the exact nature of the cast - check if the method should potentially be protected/public or if it's intentionally private
git log -p --all -S 'addAfterConfigureHandler' -- src/scripts/app.ts | head -100

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Verify the exact signature of addAfterConfigureHandler to ensure the suggested interface is correct
rg 'private addAfterConfigureHandler' src/scripts/app.ts -A3

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 281


🏁 Script executed:

#!/bin/bash
# Check what type is passed to graph.onConfigure call to understand the 'as never' cast
rg 'onConfigure.*=.*function' src/scripts/app.ts -A5

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 285


🏁 Script executed:

#!/bin/bash
# Check LGraph type definition to understand what onConfigure expects
rg 'interface LGraph|type LGraph' src/lib/litegraph/src/litegraph.ts -A15 | grep -A15 'onConfigure'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Verify if there are other similar private method test patterns we can learn from
rg '\(.*as unknown as.*\)' src/scripts/app.test.ts -B3 -A3

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Find LGraph type definition
fd 'litegraph' -t f src/lib/litegraph/src/ | head -5

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 244


🏁 Script executed:

#!/bin/bash
# Search for onConfigure property in LGraph
rg 'onConfigure' src/lib/litegraph/ -B2 -A2 | head -40

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 2008


🏁 Script executed:

#!/bin/bash
# Check the actual test helper function to understand the graph mock structure
sed -n '145,155p' src/scripts/app.test.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 313


🏁 Script executed:

#!/bin/bash
# Check what ISerialisedGraph and SerialisableGraph types are
rg 'type ISerialisedGraph|interface ISerialisedGraph' src/lib/litegraph/ -A3

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 384


🏁 Script executed:

#!/bin/bash
# Get a complete view of both test cases to check all casts
sed -n '150,180p' src/scripts/app.test.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 1082


🏁 Script executed:

#!/bin/bash
# Check if there are any other 'as any' or problematic casts in the entire test file
rg 'as any|as never' src/scripts/app.test.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 251


🏁 Script executed:

#!/bin/bash
# Verify the suggested interface in the fix matches what the method actually expects
cat > /tmp/test_interface.ts << 'EOF'
type AppWithAfterConfigureHandler = {
  addAfterConfigureHandler(graph: any): void
}
EOF
cat /tmp/test_interface.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 154


🏁 Script executed:

#!/bin/bash
# Check if there's a pattern for mocking empty graph data in other tests
rg '{} as' src/scripts/*.test.ts -B2 -A2 | head -40

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 150


🏁 Script executed:

#!/bin/bash
# Check patterns of mock data with type casts
rg 'as never\|as unknown' src/scripts/app.test.ts -B1 -A1

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Check if there's a proper type for the empty graph configuration data
rg 'ISerialisedGraph\|SerialisableGraph' src/scripts/app.test.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Review the full context - what the handler actually does with the onConfigure argument
sed -n '1,30p' src/scripts/app.ts | grep -A15 'addAfterConfigureHandler'

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 52


🏁 Script executed:

#!/bin/bash
# Final verification: Check the complete method implementation to ensure our understanding is correct
rg -A20 'private addAfterConfigureHandler\(graph: LGraph\)' src/scripts/app.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 870


🏁 Script executed:

#!/bin/bash
# Verify that ComfyApp is imported correctly in the test and understand the app instance type
sed -n '1,30p' src/scripts/app.test.ts

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 787


Fix both type assertions: replace as any with a proper type cast and address the problematic as never cast.

The (app as any) bypasses type safety. Use a narrow interface type cast instead. Additionally, graph.onConfigure?.({} as never) is problematic—the empty object should match the expected configuration data type.

🔧 Suggested fix
+    type AppWithAfterConfigureHandler = {
+      addAfterConfigureHandler(graph: LGraph): void
+    }

     it('runs legacy slot repair when mismatch is detected', () => {
       vi.mocked(hasLegacyLinkInputSlotMismatch).mockReturnValue(true)
       const graph = createConfigureGraph()

-      ;(app as any).addAfterConfigureHandler(graph)
-      graph.onConfigure?.({} as never)
+      ;(app as unknown as AppWithAfterConfigureHandler)
+        .addAfterConfigureHandler(graph)
+      graph.onConfigure?.({} as any as Parameters<typeof graph.onConfigure>[0])

       expect(hasLegacyLinkInputSlotMismatch).toHaveBeenCalledWith(graph)
       expect(fixLinkInputSlots).toHaveBeenCalledWith(graph)
     })

     it('skips legacy slot repair when no mismatch is present', () => {
       vi.mocked(hasLegacyLinkInputSlotMismatch).mockReturnValue(false)
       const graph = createConfigureGraph()

-      ;(app as any).addAfterConfigureHandler(graph)
-      graph.onConfigure?.({} as never)
+      ;(app as unknown as AppWithAfterConfigureHandler)
+        .addAfterConfigureHandler(graph)
+      graph.onConfigure?.({} as any as Parameters<typeof graph.onConfigure>[0])

       expect(hasLegacyLinkInputSlotMismatch).toHaveBeenCalledWith(graph)
       expect(fixLinkInputSlots).not.toHaveBeenCalled()
     })

Per coding guidelines src/**/*.{ts,vue}: Never use as any type; use proper TypeScript types.

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

In `@src/scripts/app.test.ts` around lines 161 - 173, Replace the unsafe casts:
instead of "(app as any).addAfterConfigureHandler(graph)" cast app to the narrow
interface that exposes addAfterConfigureHandler (e.g., an
AppWithAfterConfigureHandler type) and call addAfterConfigureHandler directly;
also replace "graph.onConfigure?.({} as never)" with a properly typed mock
configuration value that matches the expected parameter type of
graph.onConfigure (or create a minimal stub object typed to that config
interface) so both type assertions are removed and type safety is preserved for
addAfterConfigureHandler, graph, and onConfigure.

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/platform/surveys/NightlySurveyPopover.test.ts (1)

93-93: Remove the 15000ms timeout; it's unnecessary with fake timers and can mask async hangs.

The test uses vi.useFakeTimers() and advances time deterministically by 100ms with vi.advanceTimersByTimeAsync(100), so this long timeout serves no purpose and only slows failure feedback in CI. The other tests in this suite don't use explicit timeouts—keep the default instead.

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

In `@src/platform/surveys/NightlySurveyPopover.test.ts` at line 93, Remove the
explicit 15000ms Jest timeout on the test that uses vi.useFakeTimers() and
vi.advanceTimersByTimeAsync(100); locate the test block that ends with "},
15000)" in NightlySurveyPopover.test.ts and change it to simply "}" (no timeout
argument) so the test uses the default timeout and relies on fake timers for
deterministic timing.
🤖 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/platform/surveys/NightlySurveyPopover.test.ts`:
- Line 93: Remove the explicit 15000ms Jest timeout on the test that uses
vi.useFakeTimers() and vi.advanceTimersByTimeAsync(100); locate the test block
that ends with "}, 15000)" in NightlySurveyPopover.test.ts and change it to
simply "}" (no timeout argument) so the test uses the default timeout and relies
on fake timers for deterministic timing.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 025a803 and 0331c1c.

📒 Files selected for processing (2)
  • src/lib/litegraph/src/canvas/LinkConnector.core.test.ts
  • src/platform/surveys/NightlySurveyPopover.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.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/lib/litegraph/src/subgraph/SubgraphInputNode.ts (1)

206-213: ⚠️ Potential issue | 🟠 Major

Disconnect payload should reference the node input slot, not subgraph slot.

With slotType: NodeSlotType.INPUT and node, Line 212 should pass the actual
input slot object. Using subgraphInput breaks payload parity for listeners
expecting the node-input-side slot.

🔧 Suggested fix
     graphLifecycleEventDispatcher.dispatchNodeConnectionChange({
       node,
       slotType: NodeSlotType.INPUT,
       slotIndex,
       connected: false,
       link,
-      slot: subgraphInput
+      slot: input
     })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/litegraph/src/subgraph/SubgraphInputNode.ts` around lines 206 - 213,
The dispatch call in graphLifecycleEventDispatcher.dispatchNodeConnectionChange
is using the wrong slot object: when sending slotType: NodeSlotType.INPUT with
the node, pass the node-side input slot (variable input) instead of
subgraphInput so listeners receive the node input slot; update the payload
property slot: subgraphInput to slot: input while keeping node, slotIndex,
connected, and link as-is.
🤖 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/lib/litegraph/src/subgraph/SubgraphInputNode.ts`:
- Around line 181-200: The stale-link fallback in disconnectNodeInput mutates
the graph (calls subgraph.disconnectLink, removes floating links, updates
_version and setDirtyCanvas) but returns before emitting the lifecycle event;
update the fallback to call
graphLifecycleEventDispatcher.dispatchNodeConnectionChange(...) with the same
parameters used in the normal disconnect path (use the current subgraph, node
id, input id/name and the new/old connection state as appropriate) immediately
after performing subgraph.disconnectLink (and before return) so consumers
receive the node connection change; ensure you reference disconnectNodeInput,
subgraph.disconnectLink and
graphLifecycleEventDispatcher.dispatchNodeConnectionChange when making the
change.

---

Duplicate comments:
In `@src/lib/litegraph/src/subgraph/SubgraphInputNode.ts`:
- Around line 206-213: The dispatch call in
graphLifecycleEventDispatcher.dispatchNodeConnectionChange is using the wrong
slot object: when sending slotType: NodeSlotType.INPUT with the node, pass the
node-side input slot (variable input) instead of subgraphInput so listeners
receive the node input slot; update the payload property slot: subgraphInput to
slot: input while keeping node, slotIndex, connected, and link as-is.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7d737a5 and 3331ab9.

📒 Files selected for processing (3)
  • src/lib/litegraph/src/subgraph/SubgraphIO.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphInputNode.ts
  • src/lib/litegraph/src/subgraph/SubgraphOutput.ts

- LGraphNode: use link_info.origin_slot instead of array index for
  sourceSlotIndex in dispatchDisconnectNodePair
- SubgraphInputNode: pass node input slot instead of subgraphInput
  for INPUT disconnect dispatch
- SubgraphIO.test: fix double slash in import path
- NightlySurveyPopover.test: remove unnecessary 15000ms timeout
  on fake-timer test

Amp-Thread-ID: https://ampcode.com/threads/T-019c9bcb-2c95-712d-8978-ab8e9e688e4d
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.

♻️ Duplicate comments (1)
src/lib/litegraph/src/subgraph/SubgraphInputNode.ts (1)

181-200: ⚠️ Potential issue | 🟠 Major

Stale-link fallback mutates topology without lifecycle disconnect event.

The fallback path (when subgraphInput is not found) performs cleanup operations including subgraph.disconnectLink(), version increment, and setDirtyCanvas(), but returns before dispatching dispatchNodeConnectionChange. This can desync store/listener consumers that rely on lifecycle events.

Consider dispatching the lifecycle event before returning:

🔧 Suggested fix
       input.link = null
       if (link) {
         subgraph.disconnectLink(link, 'output')
         subgraph._version++
       }
       subgraph.setDirtyCanvas(false, true)
+      graphLifecycleEventDispatcher.dispatchNodeConnectionChange({
+        node,
+        slotType: NodeSlotType.INPUT,
+        slotIndex,
+        connected: false,
+        link,
+        slot: input
+      })
       return
     }

,

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

In `@src/lib/litegraph/src/subgraph/SubgraphInputNode.ts` around lines 181 - 200,
The fallback branch where subgraphInput is missing mutates topology (removing
floating links, nulling input.link, calling subgraph.disconnectLink, bumping
subgraph._version and setDirtyCanvas) but returns without emitting the lifecycle
event; update the branch in SubgraphInputNode (the block that checks if
(!subgraphInput)) to call dispatchNodeConnectionChange with the same
arguments/semantics used elsewhere in this file (after performing the
disconnect/remove operations and before returning) so listeners see the
connection change; ensure you reference input._floatingLinks,
subgraph.removeFloatingLink, subgraph.disconnectLink, and
subgraph.setDirtyCanvas as in the existing code and emit
dispatchNodeConnectionChange prior to the early return.
🧹 Nitpick comments (2)
src/lib/litegraph/src/LGraphNode.ts (1)

3111-3140: Unify non-target disconnects through the node-pair dispatcher for parity.

disconnectOutput(slot) currently uses manual per-node dispatch in this branch, while other disconnect flows use dispatchDisconnectNodePair. Using the pair dispatcher here too would reduce ordering drift risk across disconnect variants.

♻️ Proposed refactor
-          graphLifecycleEventDispatcher.dispatchNodeConnectionChange({
-            node: target,
-            slotType: NodeSlotType.INPUT,
-            slotIndex: link_info.target_slot,
-            connected: false,
-            link: link_info,
-            slot: input
-          })
-        }
-        // remove the link from the links pool
-        graph.disconnectLink(link_info, 'input')
-
-        graphLifecycleEventDispatcher.dispatchNodeConnectionChange({
-          node: this,
-          slotType: NodeSlotType.OUTPUT,
-          slotIndex: slot,
-          connected: false,
-          link: link_info,
-          slot: output
-        })
+        }
+        graph.disconnectLink(link_info, 'input')
+        if (target) {
+          graphLifecycleEventDispatcher.dispatchDisconnectNodePair({
+            sourceNode: this,
+            sourceSlotIndex: slot,
+            sourceSlot: output,
+            targetNode: target,
+            targetSlotIndex: link_info.target_slot,
+            targetSlot: input,
+            link: link_info
+          })
+        } else {
+          graphLifecycleEventDispatcher.dispatchNodeConnectionChange({
+            node: this,
+            slotType: NodeSlotType.OUTPUT,
+            slotIndex: slot,
+            connected: false,
+            link: link_info,
+            slot: output
+          })
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/litegraph/src/LGraphNode.ts` around lines 3111 - 3140,
disconnectOutput(slot) is dispatching a single-node connection change
(graphLifecycleEventDispatcher.dispatchNodeConnectionChange) for the output side
after graph.disconnectLink instead of using the existing
dispatchDisconnectNodePair helper, which other paths use; replace the manual
per-node dispatch in disconnectOutput with a call to dispatchDisconnectNodePair
(providing this node, the target node/link info, NodeSlotType.OUTPUT, slot
index, connected:false and the output slot object) so both nodes are dispatched
together and ordering matches other disconnect flows (ensure you remove the lone
dispatchNodeConnectionChange and use dispatchDisconnectNodePair consistently).
src/lib/litegraph/src/subgraph/SubgraphIO.test.ts (1)

384-452: Consider adding explicit output lifecycle ordering assertions.

You already assert input ordering (event vs callback). Mirroring that for output connect/disconnect would lock parity guarantees across both sides and reduce regressions in dispatcher ordering.

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

In `@src/lib/litegraph/src/subgraph/SubgraphIO.test.ts` around lines 384 - 452,
Add explicit assertions that verify the ordering of output connect/disconnect
lifecycle events (mirror the input ordering tests): after creating the link and
after calling subgraphOutput.disconnect(), assert the call order between
subgraph.outputNode's event and internalNode.onConnectionsChange by inspecting
outputCallback.mock.invocationCallOrder (or using toHaveBeenNthCalledWith) so
you assert which handler ran first for connect and which ran first for
disconnect; reference the symbols subgraphOutput, internalNode, outputCallback,
subgraph.outputNode, and the created link to locate where to insert these
ordering checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/lib/litegraph/src/subgraph/SubgraphInputNode.ts`:
- Around line 181-200: The fallback branch where subgraphInput is missing
mutates topology (removing floating links, nulling input.link, calling
subgraph.disconnectLink, bumping subgraph._version and setDirtyCanvas) but
returns without emitting the lifecycle event; update the branch in
SubgraphInputNode (the block that checks if (!subgraphInput)) to call
dispatchNodeConnectionChange with the same arguments/semantics used elsewhere in
this file (after performing the disconnect/remove operations and before
returning) so listeners see the connection change; ensure you reference
input._floatingLinks, subgraph.removeFloatingLink, subgraph.disconnectLink, and
subgraph.setDirtyCanvas as in the existing code and emit
dispatchNodeConnectionChange prior to the early return.

---

Nitpick comments:
In `@src/lib/litegraph/src/LGraphNode.ts`:
- Around line 3111-3140: disconnectOutput(slot) is dispatching a single-node
connection change (graphLifecycleEventDispatcher.dispatchNodeConnectionChange)
for the output side after graph.disconnectLink instead of using the existing
dispatchDisconnectNodePair helper, which other paths use; replace the manual
per-node dispatch in disconnectOutput with a call to dispatchDisconnectNodePair
(providing this node, the target node/link info, NodeSlotType.OUTPUT, slot
index, connected:false and the output slot object) so both nodes are dispatched
together and ordering matches other disconnect flows (ensure you remove the lone
dispatchNodeConnectionChange and use dispatchDisconnectNodePair consistently).

In `@src/lib/litegraph/src/subgraph/SubgraphIO.test.ts`:
- Around line 384-452: Add explicit assertions that verify the ordering of
output connect/disconnect lifecycle events (mirror the input ordering tests):
after creating the link and after calling subgraphOutput.disconnect(), assert
the call order between subgraph.outputNode's event and
internalNode.onConnectionsChange by inspecting
outputCallback.mock.invocationCallOrder (or using toHaveBeenNthCalledWith) so
you assert which handler ran first for connect and which ran first for
disconnect; reference the symbols subgraphOutput, internalNode, outputCallback,
subgraph.outputNode, and the created link to locate where to insert these
ordering checks.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3331ab9 and 2686bec.

📒 Files selected for processing (3)
  • src/lib/litegraph/src/LGraphNode.ts
  • src/lib/litegraph/src/subgraph/SubgraphIO.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphInputNode.ts

DrJKL and others added 3 commits February 26, 2026 13:38
Move private method from ComfyApp to a standalone exported utility function.

Eliminates unsafe 'as any' cast in tests by making the function directly importable.

Amp-Thread-ID: https://ampcode.com/threads/T-019c9bda-0293-7528-9b0a-f72444199cbe
Co-authored-by: Amp <amp@ampcode.com>
- switch LGraph link topology lookups and rehydration to linkStoreKey

- update litegraph tests to use graph-scoped linkStoreKey instead of graph.id

- add legacy compatibility warnings for slot dedupe, fixLinkInputSlots narrowing, and subgraph output disconnect callback parity

- initialize testing Pinia globally in vitest setup and document non-reactive link store intent

Amp-Thread-ID: https://ampcode.com/threads/T-019c9c03-dca7-757a-af61-5f41ae8d2dec
Co-authored-by: Amp <amp@ampcode.com>
- remove unused exported LinkStoreTopology type to satisfy knip pre-push checks

Amp-Thread-ID: https://ampcode.com/threads/T-019c9c03-dca7-757a-af61-5f41ae8d2dec
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

Caution

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

⚠️ Outside diff range comments (1)
src/lib/litegraph/src/subgraph/SubgraphOutput.ts (1)

55-89: ⚠️ Potential issue | 🟠 Major

Fix connect-path lifecycle parity and transaction safety.

Line 78 dispatches a connected: true event even when connectSubgraphOutputSlot(...) can return undefined (Line 38), and it passes slot instead of the peer slot (this), which is inconsistent with Line 158 in disconnect().
Also, when replacing an existing link (Line 63), no disconnected event is emitted, so callback/event parity diverges from explicit disconnect flow.
Finally, beforeChange/afterChange is not safely balanced across all paths (Line 58 vs Line 87, plus throw paths).

Proposed fix
   override connect(
     slot: INodeOutputSlot,
     node: LGraphNode,
     afterRerouteId?: RerouteId
   ): LLink | undefined {
     const { subgraph } = this.parent
@@
-    // Link should not be present, but just in case, disconnect it
-    const existingLink = this.getLinks().at(0)
-    if (existingLink != null) {
-      subgraph.beforeChange()
-
-      const { outputNode } = existingLink.resolve(subgraph)
-      if (!outputNode) throw new Error('Expected output node for existing link')
-
-      subgraph.disconnectSubgraphOutputLink(
-        this,
-        outputNode,
-        existingLink.origin_slot,
-        existingLink
-      )
-    }
-
-    const link = subgraph.connectSubgraphOutputSlot(
-      node,
-      outputIndex,
-      this,
-      afterRerouteId
-    )
-
-    graphLifecycleEventDispatcher.dispatchNodeConnectionChange({
-      node,
-      slotType: NodeSlotType.OUTPUT,
-      slotIndex: outputIndex,
-      connected: true,
-      link,
-      slot
-    })
-
-    subgraph.afterChange()
-
-    return link
+    subgraph.beforeChange()
+    try {
+      // Link should not be present, but just in case, disconnect it
+      const existingLink = this.getLinks().at(0)
+      if (existingLink != null) {
+        const { outputNode } = existingLink.resolve(subgraph)
+        if (!outputNode) throw new Error('Expected output node for existing link')
+
+        subgraph.disconnectSubgraphOutputLink(
+          this,
+          outputNode,
+          existingLink.origin_slot,
+          existingLink
+        )
+
+        graphLifecycleEventDispatcher.dispatchNodeConnectionChange({
+          node: outputNode,
+          slotType: NodeSlotType.OUTPUT,
+          slotIndex: existingLink.origin_slot,
+          connected: false,
+          link: existingLink,
+          slot: this
+        })
+      }
+
+      const link = subgraph.connectSubgraphOutputSlot(
+        node,
+        outputIndex,
+        this,
+        afterRerouteId
+      )
+      if (!link) return
+
+      graphLifecycleEventDispatcher.dispatchNodeConnectionChange({
+        node,
+        slotType: NodeSlotType.OUTPUT,
+        slotIndex: outputIndex,
+        connected: true,
+        link,
+        slot: this
+      })
+
+      return link
+    } finally {
+      subgraph.afterChange()
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/litegraph/src/subgraph/SubgraphOutput.ts` around lines 55 - 89, The
connect path must mirror disconnect behavior and be transaction-safe: when
replacing an existing link (found via getLinks().at(0) and resolved with
existingLink.resolve), call subgraph.beforeChange(), then perform
disconnectSubgraphOutputLink and emit a disconnected
graphLifecycleEventDispatcher.dispatchNodeConnectionChange (matching
disconnect() semantics), and ensure subgraph.afterChange() is always called in a
finally so before/after are balanced even on throws; when calling
connectSubgraphOutputSlot only emit a connected event if it returned a truthy
link, and pass the correct peer slot reference (use this as the peer slot
argument to graphLifecycleEventDispatcher.dispatchNodeConnectionChange rather
than slot); ensure all dispatches use NodeSlotType.OUTPUT and the proper
slotIndex/connected flags to maintain parity with disconnect().
🧹 Nitpick comments (2)
src/lib/litegraph/src/LGraph.test.ts (1)

345-346: Use createTestSubgraph() for subgraph setup in these new tests

These new setup paths instantiate subgraphs manually. Please switch to the shared helper to keep fixtures consistent and avoid setup drift across subgraph tests.

As per coding guidelines: src/lib/litegraph/**/*.test.{ts,tsx}: “Use test helper functions createTestSubgraph() and createTestSubgraphNode() from ./__fixtures__/subgraphHelpers when setting up subgraph tests”.

Also applies to: 1177-1179

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

In `@src/lib/litegraph/src/LGraph.test.ts` around lines 345 - 346, Replace the
manual subgraph setup that calls root.createSubgraph(...) and constructs nodes
like createNumberNode('subgraph-source') with the shared test helpers: use
createTestSubgraph() to create the subgraph fixture and createTestSubgraphNode()
to create subgraph nodes; update references to subgraph and subgraphSource to
use the helper-returned objects so the tests use the canonical fixture; apply
the same change at the other occurrences around the referenced block (also at
the later occurrence near lines 1177-1179) to keep subgraph tests consistent.
src/utils/graphConfigureUtil.test.ts (1)

31-35: Prefer satisfies over double-cast in the test fixture.

Line 35 uses as Partial<LGraph> as LGraph, which can mask shape drift in this helper.

Suggested refactor
 function createConfigureGraph(): LGraph {
-  return {
+  const graph = {
     nodes: [],
     onConfigure: vi.fn()
-  } as Partial<LGraph> as LGraph
+  } satisfies Pick<LGraph, 'nodes' | 'onConfigure'>
+
+  return graph as LGraph
 }

Based on learnings: In test files matching **/*.test.ts under src, when creating mock objects implementing an interface, prefer satisfies InterfaceType over double type assertions.

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

In `@src/utils/graphConfigureUtil.test.ts` around lines 31 - 35, The test helper
createConfigureGraph currently uses a double-cast ("as Partial<LGraph> as
LGraph") which can hide shape drift; replace that double-cast with TypeScript's
satisfies operator so the returned object is validated against LGraph (e.g.,
make the literal object satisfy LGraph) while still allowing a partial literal
in the test. Update the return expression in createConfigureGraph to use
"satisfies LGraph" on the object literal instead of the two as-casts, keeping
nodes: [] and onConfigure: vi.fn() intact and ensuring the file remains under
src/**/*.test.ts.
🤖 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/lib/litegraph/src/LGraph.test.ts`:
- Around line 304-318: The assertion at the end is brittle because both
linkedInput?.link and linkedInputLink can be undefined and still make the expect
pass; instead first assert the link ID exists on the restored node (call
restored.getNodeById(linkedNodeId) and ensure its inputs[0].link is defined),
then fetch linkedInputLink via restored.getLink(...) and assert the IDs match or
that linkStore.getLink(restored.linkStoreKey, linkId) equals linkedInputLink;
update the test to first expect a defined link ID from restored.getNodeById,
then compare that ID to the retrieved link instance from restored.getLink and to
linkStore.getLink to avoid false positives.

In `@src/lib/litegraph/src/LGraph.ts`:
- Around line 1704-1738: The connectSubgraphInputSlot and
connectSubgraphOutputSlot functions currently mutate slot.link and slot.linkIds
but do not call the same lifecycle event used in connectSlots; add a call to
dispatchSlotLinkChanged (the same helper used by connectSlots) after you update
subgraphInput.linkIds/targetInput.link (and similarly for the output variant) so
listeners receive the slot-link change; place the dispatch before
finalizeConnectedLink (or in the same spot connectSlots calls it) and ensure you
pass the correct node, slot index and new link id as connectSlots does.
- Around line 442-448: rehydrateLinkStore() registers graph-scoped topology
entries under linkStoreKey but never unregisters them; add a teardown path that
removes the linkStoreKey entries when the graph/subgraph is disposed. Implement
a teardown method (e.g., teardownLinkStore) that calls the inverse API on
useLinkStore() to delete/unregister this.linkStoreKey (removing
links/floatingLinks/reroutes), and invoke that method from the LGraph disposal
lifecycle (the class's dispose/destroy/close method or subgraph removal handler)
so the store does not retain stale topology entries.

In `@src/utils/graphConfigureUtil.ts`:
- Around line 25-35: The onAfterGraphConfigured callback must run even if
onConfigure throws: move the call to triggerCallbackOnAllNodes(this,
'onAfterGraphConfigured') into a finally block that always executes after
attempting onConfigure. Keep the pre-configuration steps
(hasLegacyLinkInputSlotMismatch and fixLinkInputSlots) and
triggerCallbackOnAllNodes(this, 'onGraphConfigured') before calling onConfigure;
capture the return value of onConfigure (onConfigure?.apply(this, args)) and
return it after the finally, and rethrow any exception from onConfigure so
behavior is preserved while guaranteeing triggerCallbackOnAllNodes(this,
'onAfterGraphConfigured') runs. Reference: hasLegacyLinkInputSlotMismatch,
fixLinkInputSlots, triggerCallbackOnAllNodes, onConfigure.

In `@src/utils/litegraphUtil.ts`:
- Around line 221-223: The deprecation warning is being logged inside the
recursive repair routine causing duplicate messages; modify the repair API so
warnDeprecated is emitted only once per top-level repair pass by either moving
the warnDeprecated call out of the recursive function into a non-recursive
wrapper or by adding a boolean flag param to fixLinkInputSlots (e.g., hasWarned
or warnedRef) that defaults to false, check-and-set it before calling
warnDeprecated, and pass the flag through recursive calls; update the same
pattern around the other warning sites noted (lines ~240-242) so they use the
same wrapper/flag approach.

---

Outside diff comments:
In `@src/lib/litegraph/src/subgraph/SubgraphOutput.ts`:
- Around line 55-89: The connect path must mirror disconnect behavior and be
transaction-safe: when replacing an existing link (found via getLinks().at(0)
and resolved with existingLink.resolve), call subgraph.beforeChange(), then
perform disconnectSubgraphOutputLink and emit a disconnected
graphLifecycleEventDispatcher.dispatchNodeConnectionChange (matching
disconnect() semantics), and ensure subgraph.afterChange() is always called in a
finally so before/after are balanced even on throws; when calling
connectSubgraphOutputSlot only emit a connected event if it returned a truthy
link, and pass the correct peer slot reference (use this as the peer slot
argument to graphLifecycleEventDispatcher.dispatchNodeConnectionChange rather
than slot); ensure all dispatches use NodeSlotType.OUTPUT and the proper
slotIndex/connected flags to maintain parity with disconnect().

---

Nitpick comments:
In `@src/lib/litegraph/src/LGraph.test.ts`:
- Around line 345-346: Replace the manual subgraph setup that calls
root.createSubgraph(...) and constructs nodes like
createNumberNode('subgraph-source') with the shared test helpers: use
createTestSubgraph() to create the subgraph fixture and createTestSubgraphNode()
to create subgraph nodes; update references to subgraph and subgraphSource to
use the helper-returned objects so the tests use the canonical fixture; apply
the same change at the other occurrences around the referenced block (also at
the later occurrence near lines 1177-1179) to keep subgraph tests consistent.

In `@src/utils/graphConfigureUtil.test.ts`:
- Around line 31-35: The test helper createConfigureGraph currently uses a
double-cast ("as Partial<LGraph> as LGraph") which can hide shape drift; replace
that double-cast with TypeScript's satisfies operator so the returned object is
validated against LGraph (e.g., make the literal object satisfy LGraph) while
still allowing a partial literal in the test. Update the return expression in
createConfigureGraph to use "satisfies LGraph" on the object literal instead of
the two as-casts, keeping nodes: [] and onConfigure: vi.fn() intact and ensuring
the file remains under src/**/*.test.ts.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2686bec and 9028ee9.

📒 Files selected for processing (11)
  • src/lib/litegraph/src/LGraph.test.ts
  • src/lib/litegraph/src/LGraph.ts
  • src/lib/litegraph/src/LLink.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphOutput.ts
  • src/scripts/app.test.ts
  • src/scripts/app.ts
  • src/stores/linkStore.ts
  • src/utils/graphConfigureUtil.test.ts
  • src/utils/graphConfigureUtil.ts
  • src/utils/litegraphUtil.ts
  • vitest.setup.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/scripts/app.ts
  • src/scripts/app.test.ts

DrJKL and others added 3 commits February 26, 2026 23:49
## Summary

Adds app builder in arrange/preview mode with re-orderable widgets,
maintaining size (as much as possible) between the select + preview
steps

## Changes

- **What**: 
- Extract sidebar size constants for sharing between canvas splitter +
app mode
- Add widget list using DraggableList and render inert WidgetItems

## Screenshots (if applicable)

<img width="1391" height="923" alt="image"
src="https://github.com/user-attachments/assets/3e17eafe-db1e-40a3-83b5-15a7d0672909"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9260-Render-app-builder-in-arrange-mode-3136d73d365081ef875acab683d01d9e)
by [Unito](https://www.unito.io)
…t image (#9261)

## Summary

Prevent latent previews received after the job/node has already finished
processing overwriting the actual output display

## Changes

- **What**: 
- updates job preview store to also track which node the preview was for
- updates linear progress tracking to store executed nodes enabling
skipping previews of these

## Review Focus

<!-- Critical design decisions or edge cases that need attention -->

<!-- If this PR fixes an issue, uncomment and update the line below -->
<!-- Fixes #ISSUE_NUMBER -->

## Screenshots (if applicable)

<!-- Add screenshots or video recording to help explain your changes -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9261-App-mode-discard-slow-preview-messages-to-prevent-overwriting-output-image-3136d73d3650817884c2ce2ff5993b9e)
by [Unito](https://www.unito.io)
pythongosssss and others added 11 commits February 28, 2026 14:06
## Summary

- remove exit builder button from right panel
- add builder exit button to bottom of canvas
- add builder menu with save & exit in top left

## Screenshots (if applicable)

<img width="1544" height="998" alt="image"
src="https://github.com/user-attachments/assets/f5deadc5-2bf5-4729-b644-2b6a815b9975"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9218-App-builder-exit-updates-3126d73d365081a0bf1adf92e1171060)
by [Unito](https://www.unito.io)
In adding an essentials cateogory for nodes, #8987 introduced a
regression where core nodes which are also essential are marked as being
from a `nodes` custom node instead of being marked core. Since the
essentials designation should pre-empt core and custom nodes can choose
to mark themself as essential, the getter for `isCoreNode` is updated to
instead repeat the existing check for if a node is core.

| Before | After |
| ------ | ----- |
| <img width="360" alt="before"
src="https://github.com/user-attachments/assets/f1b8bf80-d072-409a-a0f9-4837e1d11767"
/> | <img width="360" alt="after"
src="https://github.com/user-attachments/assets/14ff525b-9833-4e73-888f-791aff6cf531"/>|

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9287-Fix-essentials-nodes-not-being-marked-core-3146d73d365081fca2a0f8bdc2baf01a)
by [Unito](https://www.unito.io)
## Summary

Pre-rasterize the SubgraphNode SVG icon to a bitmap canvas to eliminate
Firefox's per-frame SVG style processing.

## Changes

- **What**: Add `getWorkflowBitmap()` that lazily rasterizes the
`data:image/svg+xml` workflow icon to an `HTMLCanvasElement` (16×16) on
first use. `SubgraphNode.drawTitleBox()` draws the cached bitmap instead
of the raw SVG.

## Review Focus

- Firefox re-processes SVG internal stylesheets (`stroke`,
`stroke-linecap`, `stroke-width`) every time `ctx.drawImage(svgImage)`
is called. Chrome caches the rasterization. This happens on every frame
for every visible SubgraphNode.
- Reporter confirmed strong subgraph correlation: "it may be happening
in the default workflow with subgraph" / "didn't seem to happen just
using manually wired up diffusion loader, clip, sampler, etc."
- Falls back to the raw SVG Image if not yet loaded or if
`getContext('2d')` returns null.

## Stack

3 of 4 in Firefox perf fix stack. Depends on #9170.

<!-- Fixes #ISSUE_NUMBER -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9172-fix-pre-rasterize-SubgraphNode-SVG-icon-to-bitmap-canvas-3116d73d365081babf17cf0848d37269)
by [Unito](https://www.unito.io)

---------

Co-authored-by: GitHub Action <action@github.com>
Patch version increment to 1.41.8

**Base branch:** `main`

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9288-1-41-8-3156d73d3650817ca737ced3e08d8c86)
by [Unito](https://www.unito.io)

---------

Co-authored-by: christian-byrne <72887196+christian-byrne@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@github.com>
## Summary

Fixes two bugs in the node replacement flow: placeholder detection
failing after workflow execution or pack reinstallation, and missing UI
sync in the Errors Tab when replacements are applied from the modal
dialog.

## Changes

- **Placeholder detection**: Node placeholder detection now matches
against `targetTypes` (derived from the replaceable node list built at
workflow load time) instead of relying on `has_errors` flag or
`registered_node_types` lookup. This ensures replacement works reliably
after execution (where `has_errors` gets cleared) and after pack
reinstallation (where the type becomes registered).
- **Modal → Errors Tab sync**: Added
`executionErrorStore.removeMissingNodesByType()` call in
`MissingNodesContent.vue` after replacement, so the Errors Tab reflects
changes immediately without requiring a page reload.

## Review Focus

- `collectAllNodes` predicate change in `useNodeReplacement.ts`: now
uses `targetTypes.has(originalType)` to find nodes by their original
serialized type. This is independent of runtime state like `has_errors`
or `registered_node_types`.
- `executionErrorStore.removeMissingNodesByType` call timing in
`MissingNodesContent.vue` — runs synchronously after
`replaceNodesInPlace` resolves, before auto-close logic.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9269-fix-node-replacement-fails-after-execution-and-modal-sync-3146d73d365081218398c961639b450f)
by [Unito](https://www.unito.io)
…omprehensive test suite (#9301)

## Summary

Resolves six open issues by reorganizing node replacement components
into a domain-driven folder structure, refactoring event handling to
follow the emit pattern, and adding comprehensive test coverage across
all affected modules.

## Changes

- **What**:
- Moved `SwapNodeGroupRow.vue` and `SwapNodesCard.vue` from
`src/components/rightSidePanel/errors/` to
`src/platform/nodeReplacement/components/` (Issues #9255)
- Moved `useMissingNodeScan.ts` from `src/composables/` to
`src/platform/nodeReplacement/missingNodeScan.ts`, renamed to reflect it
is a plain function not a Vue composable (Issues #9254)
- Refactored `SwapNodeGroupRow.vue` to emit a `'replace'` event instead
of calling `useNodeReplacement()` and `useExecutionErrorStore()`
directly; replacement logic now handled in `TabErrors.vue` (Issue #9267)
- Added unit tests for `removeMissingNodesByType`
(`executionErrorStore.test.ts`), `scanMissingNodes`
(`missingNodeScan.test.ts`), and `swapNodeGroups` computed
(`swapNodeGroups.test.ts`, `useErrorGroups.test.ts`) (Issue #9270)
- Added placeholder detection tests covering unregistered-type detection
when `has_errors` is false, and exclusion of registered types
(`useNodeReplacement.test.ts`) (Issue #9271)
- Added component tests for `MissingNodeCard` and `MissingPackGroupRow`
covering rendering, expand/collapse, events, install states, and edge
cases (Issue #9231)
- Added component tests for `SwapNodeGroupRow` and `SwapNodesCard`
(Issues #9255, #9267)

## Additional Changes (Post-Review)

- **Edge case guard in placeholder detection**
(`useNodeReplacement.ts`): When `last_serialization.type` is absent (old
serialization format), the predicate falls back to `n.type`, which the
app may have already run through `sanitizeNodeName` — stripping HTML
special characters (`& < > " ' \` =`). In that case, a `Set.has()`
lookup against the original unsanitized type name would silently miss,
causing replacement to be skipped.

Fixed by including sanitized variants of each target type in the
`targetTypes` Set at construction time. For the overwhelmingly common
case (no special characters in type names), the Set deduplicates the
entries and runtime behavior is identical to before.

A regression test was added to cover the specific scenario:
`last_serialization.type` absent + live `n.type` already sanitized.

## Review Focus

- `TabErrors.vue`: confirm the new `@replace` event handler correctly
replaces nodes and removes them from missing nodes list (mirrors the old
inline logic in `SwapNodeGroupRow`)
- `missingNodeScan.ts`: filename/export name change from
`useMissingNodeScan` — verify all call sites updated via `app.ts`
- Test mocking strategy: module-level `vi.mock()` factories use closures
over `ref`/plain objects to allow per-test overrides without global
mutable state

- Fixes #9231
- Fixes #9254
- Fixes #9255
- Fixes #9267
- Fixes #9270
- Fixes #9271
## Summary

Implement 11 Figma design discrepancies for the Node Library sidebar and
V2 Node Search dialog, aligning the UI with the [Toolbox Figma
design](https://www.figma.com/design/xMFxCziXJe6Denz4dpDGTq/Toolbox?node-id=2074-21394&m=dev).

## Changes

- **What**: Sidebar: reorder tabs (All/Essentials/Blueprints), rename
Custom→Blueprints, uppercase section headers, chevron-left of folder
icon, bookmark-on-hover for node rows, filter dropdown with checkbox
items, sort labels (Categorized/A-Z) with label-left/check-right layout,
hide section headers in A-Z mode. Search dialog: expand filter chips
from 3→6, add Recents and source categories to sidebar, remove "Filter
by" label. Pull foundation V2 components from merged PR #8548.
- **Dependencies**: Depends on #8987 (V2 Node Search) and #8548
(NodeLibrarySidebarTabV2)

## Review Focus

- Filter dropdown (`filterOptions`) is UI-scaffolded but not yet wired
to filtering logic (pending V2 integration)
- "Recents" category currently returns frequency-based results as
placeholder until a usage-tracking store is implemented
- Pre-existing type errors from V2 PR dependencies not in the base
commit (SearchBoxV2, usePerTabState, TextTicker, getProviderIcon,
getLinkTypeColor, SidebarContainerKey) are expected and will resolve
when rebased onto main after parent PRs land

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9085-feat-Node-Library-sidebar-and-V2-Search-dialog-Figma-design-improvements-30f6d73d36508175bf72d716f5904476)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Yourz <crazilou@vip.qq.com>
Co-authored-by: github-actions <github-actions@github.com>
## Summary
Send CURVE values as { __type: 'CURVE', value: [...] } instead of {
__value__: [...] } to avoid ambiguity with link detection and enable
external tools to identify the data type.

change requested by @guill

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9294-feat-wrap-CURVE-widget-value-with-typed-format-3156d73d365081bf8e5de59527e2d3ce)
by [Unito](https://www.unito.io)
## Summary

Adds a little indicator circle when new workflow menu items are added
that the user has not seen

## Changes

- **What**: Adds a hidden setting to track menu items flagged as new
that have been seen

## Screenshots (if applicable)

<img width="164" height="120" alt="image"
src="https://github.com/user-attachments/assets/ac36673d-fbf1-42ff-9a9e-1371eb96115b"
/>

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9220-Add-indicator-circle-when-new-unseen-menu-items-are-available-3126d73d3650819cb8cde854d6b6510b)
by [Unito](https://www.unito.io)
Fix multiple issues with promoted widget resolution in nested subgraphs,
ensuring correct value propagation, slot matching, and rendering for
deeply nested promoted widgets.

- **What**: Stabilize nested subgraph promoted widget resolution chain
- Use deep source keys for promoted widget values in Vue rendering mode
- Resolve effective widget options from the source widget instead of the
promoted view
  - Stabilize slot resolution for nested promoted widgets
  - Preserve combo value rendering for promoted subgraph widgets
- Prevent subgraph definition deletion while other nodes still reference
the same type
  - Clean up unused exported resolution types

- `resolveConcretePromotedWidget.ts` — new recursive resolution logic
for deeply nested promoted widgets
- `useGraphNodeManager.ts` — option extraction now uses
`effectiveWidget` for promoted widgets
- `SubgraphNode.ts` — unpack no longer force-deletes definitions
referenced by other nodes

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9282-fix-stabilize-nested-subgraph-promoted-widget-resolution-3146d73d365081208a4fe931bb7569cf)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.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.

♻️ Duplicate comments (2)
src/lib/litegraph/src/LGraph.test.ts (1)

303-318: ⚠️ Potential issue | 🟡 Minor

Harden this restore assertion to avoid undefined === undefined false positives.

The final expectation can still pass when restore failed because both linkedInput?.link and linkedInputLink are undefined. Assert existence first, then compare.

✅ Suggested fix
   const restored = new LGraph(serialised)
   const linkedInput = restored.getNodeById(linkedNodeId)?.inputs[0]
-  const linkedInputLink = restored.getLink(linkedInput?.link)
+  if (!linkedInput || linkedInput.link == null)
+    throw new Error('Expected linked input after restore')
+  const linkedInputLink = restored.getLink(linkedInput.link)
+  if (!linkedInputLink) throw new Error('Expected restored linked input link')
@@
-  expect(linkStore.getLink(restored.linkStoreKey, linkedInput?.link)).toBe(
+  expect(linkStore.getLink(restored.linkStoreKey, linkedInput.link)).toBe(
     linkedInputLink
   )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/litegraph/src/LGraph.test.ts` around lines 303 - 318, The test
currently allows a false positive when both linkedInput?.link and
linkedInputLink are undefined; first assert that linkedInput and
linkedInput?.link are defined (e.g.,
expect(restored.getNodeById(linkedNodeId)).toBeDefined() and
expect(linkedInput?.link).toBeDefined()), then fetch linkedInputLink via
restored.getLink(linkedInput!.link) and finally assert equality with
linkStore.getLink(restored.linkStoreKey, linkedInput!.link); use the LGraph,
linkedInput, linkedInputLink, getNodeById and getLink symbols to locate and
update the assertions.
src/lib/litegraph/src/LGraph.ts (1)

1714-1787: ⚠️ Potential issue | 🟠 Major

Subgraph connect helpers still skip slot-link lifecycle dispatch.

Line 1744 and Line 1782 update slot link state, but neither path emits dispatchSlotLinkChanged like connectSlots does. This keeps a parity gap for slot-link listeners on subgraph boundary connects.

🧩 Parity patch
   connectSubgraphInputSlot(
@@
     subgraphInput.linkIds.push(link.id)
     targetInput.link = link.id
+    graphLifecycleEventDispatcher.dispatchSlotLinkChanged({
+      graph: this,
+      nodeId: targetNode.id,
+      slotType: NodeSlotType.INPUT,
+      slotIndex: targetSlotIndex,
+      connected: true,
+      linkId: link.id,
+      hasWidget: !!targetInput.widget
+    })

     this.finalizeConnectedLink(link)
     return link
   }
@@
   connectSubgraphOutputSlot(
@@
     subgraphOutput.linkIds[0] = link.id
     sourceOutput.links ??= []
     sourceOutput.links.push(link.id)
+    graphLifecycleEventDispatcher.dispatchSlotLinkChanged({
+      graph: this,
+      nodeId: sourceNode.id,
+      slotType: NodeSlotType.OUTPUT,
+      slotIndex: sourceSlotIndex,
+      connected: true,
+      linkId: link.id,
+      hasWidget: false
+    })

     this.finalizeConnectedLink(link)
     return link
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/litegraph/src/LGraph.ts` around lines 1714 - 1787,
connectSubgraphInputSlot and connectSubgraphOutputSlot set link ids on subgraph
slots and node slots but never emit the slot-link lifecycle event; mirror
connectSlots by invoking dispatchSlotLinkChanged after mutating the slot link
state. Specifically, in connectSubgraphInputSlot call dispatchSlotLinkChanged
for both the subgraph input (subgraphInput.parent.id, subgraphInputIndex,
'input', link.id) and the target node input (targetNode.id, targetSlotIndex,
'input', link.id) after pushing/setting link ids; similarly, in
connectSubgraphOutputSlot call dispatchSlotLinkChanged for the source node
output (sourceNode.id, sourceSlotIndex, 'output', link.id) and the subgraph
output (subgraphOutput.parent.id, subgraphOutputIndex, 'output', link.id) after
updating their link arrays—use the same dispatchSlotLinkChanged method used by
connectSlots to ensure parity.
🧹 Nitpick comments (1)
src/lib/litegraph/src/LGraph.test.ts (1)

26-57: Scope layout mutation mocks to avoid shared file-level mutable state.

The mock functions are created once and shared across all tests in this file. This can couple suites and hide ordering/leakage issues.

♻️ Suggested refactor
+const layoutMutationsMock = vi.hoisted(() => ({
+  createLink: vi.fn(),
+  deleteLink: vi.fn(),
+  createNode: vi.fn(),
+  deleteNode: vi.fn(),
+  moveNode: vi.fn(),
+  resizeNode: vi.fn(),
+  setNodeZIndex: vi.fn(),
+  createReroute: vi.fn(),
+  deleteReroute: vi.fn(),
+  moveReroute: vi.fn(),
+  bringNodeToFront: vi.fn(),
+  setSource: vi.fn(),
+  setActor: vi.fn()
+}))

 vi.mock('@/renderer/core/layout/operations/layoutMutations', () => {
-  const createLink = vi.fn()
-  const deleteLink = vi.fn()
-  const createNode = vi.fn()
-  const deleteNode = vi.fn()
-  const moveNode = vi.fn()
-  const resizeNode = vi.fn()
-  const setNodeZIndex = vi.fn()
-  const createReroute = vi.fn()
-  const deleteReroute = vi.fn()
-  const moveReroute = vi.fn()
-  const bringNodeToFront = vi.fn()
-  const setSource = vi.fn()
-  const setActor = vi.fn()
   return {
-    useLayoutMutations: () => ({
-      createLink,
-      deleteLink,
-      createNode,
-      deleteNode,
-      moveNode,
-      resizeNode,
-      setNodeZIndex,
-      createReroute,
-      deleteReroute,
-      moveReroute,
-      bringNodeToFront,
-      setSource,
-      setActor
-    })
+    useLayoutMutations: () => layoutMutationsMock
   }
 })
+
+beforeEach(() => {
+  vi.clearAllMocks()
+})

As per coding guidelines, "For mocking in Vitest, leverage Vitest's utilities where possible; keep module mocks contained without global mutable state; use vi.hoisted() if necessary for per-test mock state manipulation."

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

In `@src/lib/litegraph/src/LGraph.test.ts` around lines 26 - 57, The current mock
creates module-level vi.fn() instances that are shared across tests (createLink,
deleteLink, createNode, etc.), causing cross-test leakage; change the mock to
produce per-test mocks by using vi.hoisted(() => vi.fn()) for each mock function
or by creating the vi.fn() instances inside a factory invoked per test and
exposing them via useLayoutMutations; update the mock returned by vi.mock to
reference those hoisted factories (or reset each mock in a beforeEach using
mockReset) so useLayoutMutations, createLink, deleteLink, createNode,
deleteNode, moveNode, resizeNode, setNodeZIndex, createReroute, deleteReroute,
moveReroute, bringNodeToFront, setSource and setActor are fresh for each test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/lib/litegraph/src/LGraph.test.ts`:
- Around line 303-318: The test currently allows a false positive when both
linkedInput?.link and linkedInputLink are undefined; first assert that
linkedInput and linkedInput?.link are defined (e.g.,
expect(restored.getNodeById(linkedNodeId)).toBeDefined() and
expect(linkedInput?.link).toBeDefined()), then fetch linkedInputLink via
restored.getLink(linkedInput!.link) and finally assert equality with
linkStore.getLink(restored.linkStoreKey, linkedInput!.link); use the LGraph,
linkedInput, linkedInputLink, getNodeById and getLink symbols to locate and
update the assertions.

In `@src/lib/litegraph/src/LGraph.ts`:
- Around line 1714-1787: connectSubgraphInputSlot and connectSubgraphOutputSlot
set link ids on subgraph slots and node slots but never emit the slot-link
lifecycle event; mirror connectSlots by invoking dispatchSlotLinkChanged after
mutating the slot link state. Specifically, in connectSubgraphInputSlot call
dispatchSlotLinkChanged for both the subgraph input (subgraphInput.parent.id,
subgraphInputIndex, 'input', link.id) and the target node input (targetNode.id,
targetSlotIndex, 'input', link.id) after pushing/setting link ids; similarly, in
connectSubgraphOutputSlot call dispatchSlotLinkChanged for the source node
output (sourceNode.id, sourceSlotIndex, 'output', link.id) and the subgraph
output (subgraphOutput.parent.id, subgraphOutputIndex, 'output', link.id) after
updating their link arrays—use the same dispatchSlotLinkChanged method used by
connectSlots to ensure parity.

---

Nitpick comments:
In `@src/lib/litegraph/src/LGraph.test.ts`:
- Around line 26-57: The current mock creates module-level vi.fn() instances
that are shared across tests (createLink, deleteLink, createNode, etc.), causing
cross-test leakage; change the mock to produce per-test mocks by using
vi.hoisted(() => vi.fn()) for each mock function or by creating the vi.fn()
instances inside a factory invoked per test and exposing them via
useLayoutMutations; update the mock returned by vi.mock to reference those
hoisted factories (or reset each mock in a beforeEach using mockReset) so
useLayoutMutations, createLink, deleteLink, createNode, deleteNode, moveNode,
resizeNode, setNodeZIndex, createReroute, deleteReroute, moveReroute,
bringNodeToFront, setSource and setActor are fresh for each test.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b9c4675 and 0a940d6.

📒 Files selected for processing (5)
  • src/lib/litegraph/src/LGraph.test.ts
  • src/lib/litegraph/src/LGraph.ts
  • src/lib/litegraph/src/subgraph/SubgraphInput.ts
  • src/lib/litegraph/src/subgraph/SubgraphNode.ts
  • src/scripts/app.ts

- Replace deprecated indexed link access with Map#get in SubgraphOutput.disconnect

- Add behavior-focused regression coverage for idempotent output disconnect

Amp-Thread-ID: https://ampcode.com/threads/T-019ca639-cde7-744d-b426-6ea21b2159e6
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.

🧹 Nitpick comments (1)
src/lib/litegraph/src/subgraph/SubgraphOutput.ts (1)

133-135: Consider controlling deprecation warning emission.

The warnDeprecated call executes on every disconnect() invocation, which may be noisy in production or during automated workflows. Consider gating this behind a development/debug flag or emitting only once per session.

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

In `@src/lib/litegraph/src/subgraph/SubgraphOutput.ts` around lines 133 - 135, The
warnDeprecated call inside SubgraphOutput.disconnect should not fire on every
call; modify the disconnect method to emit that deprecation warning only once
per session or only in development by introducing a guard (e.g., a module-level
or static flag like warnedSubgraphOutputDisconnect or
SubgraphOutput._warnedDisconnect) and/or check a debug/dev flag before calling
warnDeprecated; update SubgraphOutput.disconnect to check the guard/flag, call
warnDeprecated only the first time (or when process.env.NODE_ENV !==
'production' / a provided debug flag is true), and set the guard so subsequent
disconnect calls are silent.
🤖 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/lib/litegraph/src/subgraph/SubgraphOutput.ts`:
- Around line 133-135: The warnDeprecated call inside SubgraphOutput.disconnect
should not fire on every call; modify the disconnect method to emit that
deprecation warning only once per session or only in development by introducing
a guard (e.g., a module-level or static flag like warnedSubgraphOutputDisconnect
or SubgraphOutput._warnedDisconnect) and/or check a debug/dev flag before
calling warnDeprecated; update SubgraphOutput.disconnect to check the
guard/flag, call warnDeprecated only the first time (or when
process.env.NODE_ENV !== 'production' / a provided debug flag is true), and set
the guard so subsequent disconnect calls are silent.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a940d6 and d6e05bd.

📒 Files selected for processing (2)
  • src/lib/litegraph/src/subgraph/SubgraphIO.test.ts
  • src/lib/litegraph/src/subgraph/SubgraphOutput.ts

graph.linkStore was removed; use graph.links, graph.floatingLinks, and graph.reroutes maps directly to verify store-backed accessor parity.

Amp-Thread-ID: https://ampcode.com/threads/T-019ca6ac-9a9e-7139-8b31-dee2bec82341
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.

🧹 Nitpick comments (2)
browser_tests/tests/changeTracker.spec.ts (2)

105-125: Use retrying assertions after programmatic undo/redo.

These post-undo/redo snapshot checks are immediate; switching to expect.poll makes the test less timing-sensitive if topology projection settles on a later tick.

Suggested patch
-      const afterUndo = await readTopology()
-      expect(afterUndo.links).toBe(baseline.links)
-      expect(afterUndo.floatingLinks).toBe(baseline.floatingLinks)
-      expect(afterUndo.reroutes).toBe(baseline.reroutes)
-      expect(afterUndo.serialised).toEqual(baseline.serialised)
+      await expect.poll(readTopology).toEqual(baseline)
@@
-      const afterRedo = await readTopology()
-      expect(afterRedo.links).toBe(mutated.links)
-      expect(afterRedo.floatingLinks).toBe(mutated.floatingLinks)
-      expect(afterRedo.reroutes).toBe(mutated.reroutes)
-      expect(afterRedo.serialised).toEqual(mutated.serialised)
+      await expect.poll(readTopology).toEqual(mutated)

As per coding guidelines, browser_tests/**/*.spec.ts should follow Playwright best practices, including retrying assertions for async behavior.

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

In `@browser_tests/tests/changeTracker.spec.ts` around lines 105 - 125, The
immediate assertions after calling changeTracker.undo()/redo() are brittle; wrap
the topology checks in Playwright's retrying assertions (expect.poll) so the
test waits for projection to settle. Specifically, after calling
comfyPage.page.evaluate that invokes (window.app!.extensionManager as
WorkspaceStore).workflow.activeWorkflow?.changeTracker.undo() or .redo(),
replace the direct readTopology() + expect(...) checks with expect.poll(() =>
readTopology()).toHaveProperty/... or a poll that returns the topology and
assert links, floatingLinks, reroutes and serialised against baseline/mutated;
keep the same identifiers (comfyPage.page.evaluate, WorkspaceStore,
changeTracker.undo/redo, readTopology) so the logic location is obvious.

86-99: Consider asserting before/after callback ordering in this path.

Given the PR’s compatibility goal around lifecycle ordering, adding an explicit sequence assertion here would lock in the beforeChange -> mutation -> afterChange contract beyond queue-size checks.

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

In `@browser_tests/tests/changeTracker.spec.ts` around lines 86 - 99, Add an
explicit ordering assertion around the beforeChange -> mutation -> afterChange
lifecycle: in the test that calls beforeChange(comfyPage), runs
comfyPage.page.evaluate(...) (which uses window.app!.rootGraph.createReroute,
addFloatingLink, toFloating) and then afterChange(comfyPage), record a simple
ordering flag or timestamp in the page context (e.g. set window.__changeSequence
before/after and during the mutation) and assert in the test that the
flag/timestamps show beforeChange ran first, the mutation ran second, and
afterChange ran last; update the assertion near the existing calls to
beforeChange, comfyPage.page.evaluate, and afterChange to fail if the sequence
is violated.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@browser_tests/tests/changeTracker.spec.ts`:
- Around line 105-125: The immediate assertions after calling
changeTracker.undo()/redo() are brittle; wrap the topology checks in
Playwright's retrying assertions (expect.poll) so the test waits for projection
to settle. Specifically, after calling comfyPage.page.evaluate that invokes
(window.app!.extensionManager as
WorkspaceStore).workflow.activeWorkflow?.changeTracker.undo() or .redo(),
replace the direct readTopology() + expect(...) checks with expect.poll(() =>
readTopology()).toHaveProperty/... or a poll that returns the topology and
assert links, floatingLinks, reroutes and serialised against baseline/mutated;
keep the same identifiers (comfyPage.page.evaluate, WorkspaceStore,
changeTracker.undo/redo, readTopology) so the logic location is obvious.
- Around line 86-99: Add an explicit ordering assertion around the beforeChange
-> mutation -> afterChange lifecycle: in the test that calls
beforeChange(comfyPage), runs comfyPage.page.evaluate(...) (which uses
window.app!.rootGraph.createReroute, addFloatingLink, toFloating) and then
afterChange(comfyPage), record a simple ordering flag or timestamp in the page
context (e.g. set window.__changeSequence before/after and during the mutation)
and assert in the test that the flag/timestamps show beforeChange ran first, the
mutation ran second, and afterChange ran last; update the assertion near the
existing calls to beforeChange, comfyPage.page.evaluate, and afterChange to fail
if the sequence is violated.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d6e05bd and 767f19f.

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

@DrJKL DrJKL requested a review from AustinMroz March 1, 2026 01:54
DrJKL and others added 7 commits February 28, 2026 18:01
- Fix copy-paste error in SubgraphOutput type error message
- Add linkStore.clearGraph() call in LGraph.clear() to prevent memory leak
- Cache useLinkStore() reference on LGraph instance for hot-path perf
- Use frozen EMPTY_TOPOLOGY sentinel instead of allocating per miss
- Make canonicalName required in SubgraphEventMap (always provided)
- Replace deprecated links[linkId] with links.get() in SubgraphNode
- Document connectSlots non-nullable return contract

Amp-Thread-ID: https://ampcode.com/threads/T-019ca6ba-5e48-707b-8c81-3eda7f6bc9e2
Co-authored-by: Amp <amp@ampcode.com>
- Remove redundant storeNodeId and storeName from SafeWidgetData
- Extract buildSlotMetadata helper to deduplicate slot metadata logic
- Simplify normalizeWidgetValue to collapse identity branches
- Collapse duplicated flags.* property handlers into single case
- Merge color/bgcolor handlers with computed property key
- Remove redundant syncWithGraph (existing-nodes loop covers it)
- Inline trigger dispatch, remove intermediate triggerHandlers map
- Avoid duplicate extractWidgetDisplayOptions calls in common path
- Remove unused LGraphTriggerAction import

Amp-Thread-ID: https://ampcode.com/threads/T-019ca721-4ac5-730e-a526-6c71e474e766
Co-authored-by: Amp <amp@ampcode.com>
- S1: Extract _createAndRegisterLink helper from 3 connect methods
- S2: Remove dead SubgraphInputNode.connectSlots (unregistered LLinks)
- S3: Document that LGraph.connectSlots callers dispatch callbacks
- S4: Add dispatchSlotLinkChanged to connectSubgraphInputSlot
- S5: Document _version increment contract on disconnect paths
- S6: Add backwards-compat comments at 3 behavioral change sites
- S7: Gate SubgraphOutput.disconnect deprecation warning with once flag
- S8: Fix splitPositionables instanceof ordering (subclass before base)
- S9: Add explicit return type to mapReroutes
- S10: Add JSDoc to LinkStoreTopology about mutable backing maps
- S11: Document dispatch ordering asymmetry in event dispatcher

Amp-Thread-ID: https://ampcode.com/threads/T-019ca787-c4d4-755f-b63a-4d814ff46e2c
Co-authored-by: Amp <amp@ampcode.com>
- L7: Extract resolveCanonicalSlotName and normalizeLegacySlotIdentity to utils/slotIdentity.ts (-45 lines from LGraph.ts)

- L3: Inline 4 trivial subgraphBoundaryAdapter wrappers using existing LLink getters (-26 lines from subgraphUtils.ts)

- L4: Merge hasLegacyLinkInputSlotMismatch into fixLinkInputSlots single-pass (-17 lines, eliminates double traversal)

Amp-Thread-ID: https://ampcode.com/threads/T-019ca83b-1182-77df-b270-4703bb00cf45
Co-authored-by: Amp <amp@ampcode.com>
- Emit dispatchNodeConnectionChange in SubgraphInputNode stale-link fallback
- Balance beforeChange/afterChange with try/finally in SubgraphOutput.connect
- Emit disconnect event when SubgraphOutput.connect replaces existing link
- Guard connectSubgraphOutputSlot return and skip dispatch on falsy link
- Move onAfterGraphConfigured to finally block in graphConfigureUtil
- Add dispatchSlotLinkChanged to connectSubgraphOutputSlot for parity
- Guard remapProxyWidgets call with explicit map-entry check
- Harden LinkStore restore test assertions with existence checks
- Use dispatchDisconnectNodePair in disconnectOutput for consistency

Amp-Thread-ID: https://ampcode.com/threads/T-019cb1e7-a18e-712a-b8c7-4448f603c8b1
Co-authored-by: Amp <amp@ampcode.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants