Skip to content

Comments

feat: synthetic widgets getter for SubgraphNode (proxy-widget-v2)#8856

Merged
DrJKL merged 95 commits intomainfrom
drjkl/subgraphs-are-the-best
Feb 23, 2026
Merged

feat: synthetic widgets getter for SubgraphNode (proxy-widget-v2)#8856
DrJKL merged 95 commits intomainfrom
drjkl/subgraphs-are-the-best

Conversation

@DrJKL
Copy link
Contributor

@DrJKL DrJKL commented Feb 14, 2026

Summary

Replace the Proxy-based proxy widget system with a store-driven architecture where promotionStore and widgetValueStore are the single sources of truth for subgraph widget promotion and widget values, and SubgraphNode.widgets is a synthetic getter composing lightweight PromotedWidgetView objects from store state.

Motivation

The subgraph widget promotion system previously scattered state across multiple unsynchronized layers:

  • Persistence: node.properties.proxyWidgets (tuples on the LiteGraph node)
  • Runtime: Proxy-based proxyWidget.ts with Overlay objects, DisconnectedWidget singleton, and isProxyWidget type guards
  • UI: Each Vue component independently calling parseProxyWidgets() via customRef hacks
  • Mutation flags: Imperative widget.promoted = true/false set on subgraph-opened events

This led to 4+ independent parsings of the same data, complex cache invalidation, and no reactive contract between the promotion state and the rendering layer. Widget values were similarly owned by LiteGraph with no Vue-reactive backing.

The core principle driving these changes: Vue owns truth. Pinia stores are the canonical source; LiteGraph objects delegate to stores via getters/setters; Vue components react to store state directly.

Changes

New stores (single sources of truth)

  • promotionStore — Reactive Map<NodeId, PromotionEntry[]> tracking which interior widgets are promoted on which SubgraphNode instances. Graph-scoped by root graph ID to prevent cross-workflow state collision. Replaces properties.proxyWidgets parsing, customRef hacks, widget.promoted mutation, and the subgraph-opened event listener.
  • widgetValueStore — Graph-scoped Map<WidgetKey, WidgetState> that is the canonical owner of widget values. BaseWidget.value delegates to this store via getter/setter when a node ID is assigned. Eliminates the need for Proxy-based value forwarding.

Synthetic widgets getter (SubgraphNode)

SubgraphNode.widgets is now a getter that reads promotionStore.getPromotions(rootGraphId, nodeId) and returns cached PromotedWidgetView objects. No stubs, no Proxies, no fake widgets persisted in the array. The setter is a no-op — mutations go through promotionStore.

PromotedWidgetView

A class behind a createPromotedWidgetView factory, implementing the PromotedWidgetView interface. Delegates value/type/options/drawing to the resolved interior widget and stores. Owns positional state (y, computedHeight) for canvas layout. Cached by PromotedWidgetViewManager for object-identity stability across frames.

DOM widget promotion

Promoted DOM widgets (textarea, image upload, etc.) render on the SubgraphNode surface via positionOverride in domWidgetStore. DomWidgets.vue checks for overrides and uses the SubgraphNode's coordinates instead of the interior node's.

Promoted previews

New usePromotedPreviews composable resolves image/audio/video preview widgets from promoted entries, enabling SubgraphNodes to display previews of interior preview nodes.

Deleted

  • proxyWidget.ts (257 lines) — Proxy handler, Overlay, newProxyWidget, isProxyWidget
  • DisconnectedWidget.ts (39 lines) — Singleton Proxy target
  • useValueTransform.ts (32 lines) — Replaced by store delegation

Key architectural changes

  • BaseWidget.value getter/setter delegates to widgetValueStore when node ID is set
  • LGraph.add() reordered: node.graph assigned before widget setNodeId (enables store registration)
  • LGraph.clear() cleans up graph-scoped stores to prevent stale entries across workflow switches
  • promotionStore and widgetValueStore state nested under root graph UUID for multi-workflow isolation
  • SubgraphNode.serialize() writes promotions back to properties.proxyWidgets for persistence compatibility
  • Legacy -1 promotion entries resolved and migrated on first load with dev warning

Test coverage

  • 3,700+ lines of new/updated tests across 36 test files
  • Unit: promotionStore.test.ts, widgetValueStore.test.ts, promotedWidgetView.test.ts (921 lines), subgraphNodePromotion.test.ts, proxyWidgetUtils.test.ts, DomWidgets.test.ts, PromotedWidgetViewManager.test.ts, usePromotedPreviews.test.ts, resolvePromotedWidget.test.ts, subgraphPseudoWidgetCache.test.ts
  • E2E: subgraphPromotion.spec.ts (622 lines) — promote/demote, manual/auto promotion, paste preservation, seed control augmentation, image preview promotion; imagePreview.spec.ts extended with multi-promoted-preview coverage
  • Fixtures: 2 new subgraph workflow fixtures for preview promotion scenarios

Review focus

  • Graph-scoped store keying (rootGraphId) — verify isolation across workflows/tabs and cleanup on LGraph.clear()
  • PromotedWidgetView positional stability — _arrangeWidgets writes to y/computedHeight on cached objects; getter returns fresh array but stable object references
  • DOM widget position override lifecycle — overrides set on promote, cleared on demote/removal/subgraph navigation
  • Legacy -1 entry migration — resolved and written back on first load; unresolvable entries dropped with dev warning
  • Serialization round-trip — promotionStore state → properties.proxyWidgets on serialize, hydrated back on configure

Diff breakdown (excluding lockfile)

  • 153 files changed, ~7,500 insertions, ~1,900 deletions (excluding pnpm-lock.yaml churn)
  • ~3,700 lines are tests
  • ~300 lines deleted (proxyWidget.ts, DisconnectedWidget.ts, useValueTransform.ts)

┆Issue is synchronized with this Notion page by Unito

@github-actions
Copy link

github-actions bot commented Feb 14, 2026

🎭 Playwright: ✅ 539 passed, 0 failed · 9 flaky

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

@github-actions
Copy link

github-actions bot commented Feb 14, 2026

🎨 Storybook: ✅ Built — View Storybook

Details

⏰ Completed at: 02/23/2026, 08:10:49 PM UTC

Links

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 14, 2026

📝 Walkthrough

Walkthrough

Replaces legacy proxy-widget system with a promotion store and PromotedWidgetView; adds promotion-aware DOM positioning, transition-grace, promoted previews (image/video/audio), promotion-driven outline coloring, refactors SubgraphNode/widget plumbing, and includes broad UI, store, test, and fixture updates.

Changes

Cohort / File(s) Summary
Promotion store & types
src/stores/promotionStore.ts, src/stores/promotionStore.test.ts, src/core/graph/subgraph/promotedWidgetTypes.ts
Add Pinia promotion store, reference-counting, helpers and tests; add PromotedWidgetView type guard.
Promoted view implementation & tests
src/core/graph/subgraph/promotedWidgetView.ts, src/core/graph/subgraph/promotedWidgetView.test.ts
New PromotedWidgetView factory that delegates drawing, events, value storage, DOM override sync, and related tests.
Subgraph core
src/lib/litegraph/src/subgraph/SubgraphNode.ts, src/lib/litegraph/src/infrastructure/SubgraphInputEventMap.ts
Expose synthetic widgets accessor backed by promoted views, add view cache, legacy migration, DOM override cleanup, and extend input-connected payload.
Proxy removal & utils
src/core/graph/subgraph/proxyWidget.ts (removed), src/core/graph/subgraph/proxyWidgetUtils.ts, src/core/graph/subgraph/proxyWidget.test.ts
Remove legacy proxy module; migrate utilities to promotion-store flows, add getWidgetName/isPreviewPseudoWidget, update tests.
DOM widget system & z-index
src/components/graph/DomWidgets.vue, src/components/graph/widgets/DomWidget.vue, src/components/graph/widgets/domWidgetZIndex.ts, src/stores/domWidgetStore.ts, src/components/graph/DomWidgets.test.ts, src/components/graph/widgets/domWidgetZIndex.test.ts
Add positionOverride support, transition-grace handling, getDomWidgetZIndex utility, selection owner resolution, and tests for transitions and z-index.
Right-side UI & editors
src/components/rightSidePanel/...
SectionWidgets.vue, TabSubgraphInputs.vue, WidgetActions.vue, WidgetItem.vue, SubgraphEditor.vue, RightSidePanel.vue, WidgetItem.test.ts
Rewire UI to use promotionStore (promote/demote/movePromotion), remove proxyWidgets emits, resolve promoted sources, and update widget abstraction and tests.
Widget base drawing & outline
src/lib/litegraph/src/widgets/BaseWidget.ts, multiple *Widget.ts
Remove public promoted flag, add getOutlineColor(suppressPromotedOutline) that consults promotionStore, propagate suppressPromotedOutline through widget draw APIs.
Stores & state types
src/stores/widgetValueStore.ts, src/stores/widgetValueStore.test.ts, src/stores/nodeDefStore.ts
Remove promoted from WidgetState public pick; adjust nodeDefStore to use promoted view shape and sourceNodeId/sourceWidgetName.
Promoted previews & integration
src/composables/node/usePromotedPreviews.ts, src/composables/node/usePromotedPreviews.test.ts, src/renderer/core/canvas/canvasStore.ts, src/services/litegraphService.ts
Add composable to surface promoted previews (image/video/audio), wire preview updates into canvas/service flows, and hook promoteRecommendedWidgets on subgraph conversion.
Vue node components & widgets
src/renderer/extensions/vueNodes/components/..., src/renderer/extensions/vueNodes/widgets/components/...
Integrate promotionStore in node rendering (promoted previews, border styles), add AudioPreview, adjust ImagePreview layout, and add promoted resolution for WidgetDOM/WidgetLegacy.
Image upload & removed util
src/renderer/extensions/vueNodes/widgets/composables/useImageUploadWidget.ts, src/composables/useValueTransform.ts (removed)
Remove useValueTransform, simplify upload widget value handling and formatting.
Scripts & canvas integration
src/scripts/app.ts, src/renderer/core/canvas/canvasStore.ts
Remove registerProxyWidgets call at startup; add promoteRecommendedWidgets listener on subgraph-converted.
Tests, fixtures & browser tests
browser_tests/... (many files)
Large set of unit and E2E tests, new fixtures for preview subgraphs, helpers for promoted widget queries, and test tweaks (polling, getNodeCount).
CI & tooling
.github/workflows/ci-lint-format.yaml, lint-staged.config.ts
Add conditional browser_tests typecheck in CI and lint-staged to run browser typecheck when relevant files are staged.
Removed legacy widget
src/lib/litegraph/src/widgets/DisconnectedWidget.ts (removed)
Deleted DisconnectedWidget module and singleton.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SubgraphNode
    participant PromotionStore
    participant PromotedView
    participant DomWidgetStore
    participant VueComp

    User->>SubgraphNode: Convert node → subgraph
    SubgraphNode->>PromotionStore: promoteRecommendedWidgets(subgraph)
    PromotionStore->>PromotionStore: promote(subgraphId, interiorNodeId, widgetName)
    SubgraphNode->>PromotedView: createPromotedWidgetView(...)
    PromotedView->>DomWidgetStore: syncDomOverride(node, widget)
    DomWidgetStore-->>PromotedView: positionOverride registered

    User->>VueComp: Interact with UI
    VueComp->>PromotionStore: isPromoted(subgraphId, interiorNodeId, widgetName)?
    PromotionStore-->>VueComp: true/false
    VueComp->>PromotedView: render / style selection
    User->>VueComp: Demote widget
    VueComp->>PromotionStore: demote(...)
    PromotionStore->>PromotedView: onRemoved()
    PromotedView->>DomWidgetStore: clearPositionOverride(widgetId)
Loading
sequenceDiagram
    participant DomWidgetsComp
    participant DomWidgetStore
    participant Canvas

    DomWidgetsComp->>DomWidgetStore: read widgets + positionOverride
    alt positionOverride active (same graph)
        DomWidgetsComp->>DomWidgetsComp: useOverride = true
        DomWidgetsComp->>DomWidgetsComp: compute position from override.node/widget
    else override exists but not active
        DomWidgetsComp->>DomWidgetsComp: add widgetId to transitionGrace
        DomWidgetsComp->>DomWidgetsComp: render using override position for 1 frame
    end
    DomWidgetsComp->>Canvas: drawForeground (apply z-index via getDomWidgetZIndex)
    Canvas->>DomWidgetsComp: next frame tick
    DomWidgetsComp->>DomWidgetsComp: remove expired transitionGrace entries
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

size:XL

Suggested reviewers

  • AustinMroz

Poem

🐰 I hopped through subgraphs, quick and spry,

Turned old proxies into stores with a twitch of my eye.
Previews now sing—image, audio, frame—
Promoted widgets dancing without any shame.
Cheers from the burrow: the garden's aflutter, hi-fi!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.07% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: synthetic widgets getter for SubgraphNode (proxy-widget-v2)' accurately describes the main architectural change, which is replacing the proxy-based system with a synthetic widgets getter backed by store-driven state.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering summary, motivation, detailed changes (new stores, synthetic getter, PromotedWidgetView, DOM widget promotion, deleted files), architectural changes, test coverage, review focus areas, and diff breakdown.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch drjkl/subgraphs-are-the-best

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

@github-actions
Copy link

github-actions bot commented Feb 14, 2026

📦 Bundle: 4.39 MB gzip 🔴 +7.49 kB

Details

Summary

  • Raw size: 20.6 MB baseline 20.6 MB — 🔴 +25.3 kB
  • Gzip: 4.39 MB baseline 4.38 MB — 🔴 +7.49 kB
  • Brotli: 3.39 MB baseline 3.38 MB — 🔴 +4.42 kB
  • Bundles: 224 current • 224 baseline • 150 added / 150 removed

Category Glance
Data & Services 🔴 +19.1 kB (2.54 MB) · Graph Workspace 🔴 +5.46 kB (968 kB) · Other 🔴 +420 B (7.62 MB) · Vendor & Third-Party 🔴 +389 B (8.84 MB) · Panels & Settings 🟢 -19 B (436 kB) · Views & Navigation 🟢 -11 B (68.8 kB) · + 5 more

App Entry Points — 17.9 kB (baseline 17.9 kB) • 🟢 -4 B

Main entry bundles and manifests

File Before After Δ Raw Δ Gzip Δ Brotli
assets/index-CXFf1qdP.js (removed) 17.9 kB 🟢 -17.9 kB 🟢 -6.36 kB 🟢 -5.48 kB
assets/index-B6Q38EIX.js (new) 17.9 kB 🔴 +17.9 kB 🔴 +6.34 kB 🔴 +5.52 kB

Status: 1 added / 1 removed

Graph Workspace — 968 kB (baseline 962 kB) • 🔴 +5.46 kB

Graph editor runtime, canvas, workflow orchestration

File Before After Δ Raw Δ Gzip Δ Brotli
assets/GraphView-BDXtuczy.js (new) 968 kB 🔴 +968 kB 🔴 +208 kB 🔴 +158 kB
assets/GraphView-BPsho2Z-.js (removed) 962 kB 🟢 -962 kB 🟢 -207 kB 🟢 -157 kB

Status: 1 added / 1 removed

Views & Navigation — 68.8 kB (baseline 68.8 kB) • 🟢 -11 B

Top-level views, pages, and routed surfaces

File Before After Δ Raw Δ Gzip Δ Brotli
assets/CloudSurveyView-A00RLH2g.js (removed) 15.5 kB 🟢 -15.5 kB 🟢 -3.32 kB 🟢 -2.83 kB
assets/CloudSurveyView-D3nPxmNt.js (new) 15.5 kB 🔴 +15.5 kB 🔴 +3.31 kB 🔴 +2.81 kB
assets/CloudLoginView-BymyXCKc.js (removed) 10 kB 🟢 -10 kB 🟢 -2.93 kB 🟢 -2.58 kB
assets/CloudLoginView-CfO-rn-g.js (new) 10 kB 🔴 +10 kB 🔴 +2.92 kB 🔴 +2.57 kB
assets/UserCheckView-BAdsTbUl.js (new) 8.41 kB 🔴 +8.41 kB 🔴 +2.23 kB 🔴 +1.94 kB
assets/UserCheckView-D-a32UQX.js (removed) 8.41 kB 🟢 -8.41 kB 🟢 -2.23 kB 🟢 -1.94 kB
assets/CloudSignupView-90LhEtWs.js (removed) 7.41 kB 🟢 -7.41 kB 🟢 -2.32 kB 🟢 -2.02 kB
assets/CloudSignupView-DvJtbxd3.js (new) 7.41 kB 🔴 +7.41 kB 🔴 +2.32 kB 🔴 +2.04 kB
assets/CloudLayoutView-DDZw7I4d.js (removed) 6.43 kB 🟢 -6.43 kB 🟢 -2.1 kB 🟢 -1.82 kB
assets/CloudLayoutView-DmlQEIT3.js (new) 6.43 kB 🔴 +6.43 kB 🔴 +2.1 kB 🔴 +1.82 kB
assets/CloudForgotPasswordView-Q4M1Rjkv.js (removed) 5.56 kB 🟢 -5.56 kB 🟢 -1.93 kB 🟢 -1.71 kB
assets/CloudForgotPasswordView-pXG1cNQY.js (new) 5.56 kB 🔴 +5.56 kB 🔴 +1.93 kB 🔴 +1.71 kB
assets/CloudAuthTimeoutView-DC3UViTv.js (removed) 4.91 kB 🟢 -4.91 kB 🟢 -1.77 kB 🟢 -1.55 kB
assets/CloudAuthTimeoutView-J2qE71-N.js (new) 4.91 kB 🔴 +4.91 kB 🔴 +1.77 kB 🔴 +1.55 kB
assets/CloudSubscriptionRedirectView-BeyWjD78.js (removed) 4.71 kB 🟢 -4.71 kB 🟢 -1.78 kB 🟢 -1.57 kB
assets/CloudSubscriptionRedirectView-BkAxYJf7.js (new) 4.71 kB 🔴 +4.71 kB 🔴 +1.78 kB 🔴 +1.57 kB
assets/UserSelectView-Byi7fEUL.js (new) 4.5 kB 🔴 +4.5 kB 🔴 +1.64 kB 🔴 +1.47 kB
assets/UserSelectView-CBg5HTyp.js (removed) 4.5 kB 🟢 -4.5 kB 🟢 -1.64 kB 🟢 -1.46 kB
assets/CloudSorryContactSupportView-B44yD6kN.js (new) 1.02 kB 🔴 +1.02 kB 🔴 +541 B 🔴 +463 B
assets/CloudSorryContactSupportView-Dd0hIqYZ.js (removed) 1.02 kB 🟢 -1.02 kB 🟢 -540 B 🟢 -463 B
assets/layout-CIz0E8h3.js (removed) 296 B 🟢 -296 B 🟢 -225 B 🟢 -194 B
assets/layout-Cr8hM5-r.js (new) 296 B 🔴 +296 B 🔴 +224 B 🔴 +189 B

Status: 11 added / 11 removed

Panels & Settings — 436 kB (baseline 436 kB) • 🟢 -19 B

Configuration panels, inspectors, and settings screens

File Before After Δ Raw Δ Gzip Δ Brotli
assets/SecretsPanel-COKh21zo.js (removed) 21.5 kB 🟢 -21.5 kB 🟢 -5.3 kB 🟢 -4.65 kB
assets/SecretsPanel-CqVuMKqt.js (new) 21.5 kB 🔴 +21.5 kB 🔴 +5.31 kB 🔴 +4.66 kB
assets/LegacyCreditsPanel-DhD5uT54.js (removed) 20.6 kB 🟢 -20.6 kB 🟢 -5.56 kB 🟢 -4.89 kB
assets/LegacyCreditsPanel-DXRnJClB.js (new) 20.6 kB 🔴 +20.6 kB 🔴 +5.57 kB 🔴 +4.9 kB
assets/SubscriptionPanel-E22Q2iaC.js (removed) 18.6 kB 🟢 -18.6 kB 🟢 -4.73 kB 🟢 -4.17 kB
assets/SubscriptionPanel-Rl4uk-_W.js (new) 18.6 kB 🔴 +18.6 kB 🔴 +4.73 kB 🔴 +4.17 kB
assets/KeybindingPanel-BW-mfD3Y.js (removed) 12.3 kB 🟢 -12.3 kB 🟢 -3.57 kB 🟢 -3.17 kB
assets/KeybindingPanel-v4wW3xpW.js (new) 12.3 kB 🔴 +12.3 kB 🔴 +3.57 kB 🔴 +3.17 kB
assets/AboutPanel-DeiMH_wh.js (removed) 9.79 kB 🟢 -9.79 kB 🟢 -2.73 kB 🟢 -2.47 kB
assets/AboutPanel-CjeTguGl.js (new) 9.79 kB 🔴 +9.79 kB 🔴 +2.73 kB 🔴 +2.46 kB
assets/ExtensionPanel-Bg1XZyMn.js (removed) 9.38 kB 🟢 -9.38 kB 🟢 -2.65 kB 🟢 -2.36 kB
assets/ExtensionPanel-CHwDAa9X.js (new) 9.38 kB 🔴 +9.38 kB 🔴 +2.65 kB 🔴 +2.35 kB
assets/ServerConfigPanel-DKjzff4n.js (removed) 6.44 kB 🟢 -6.44 kB 🟢 -2.12 kB 🟢 -1.9 kB
assets/ServerConfigPanel-CJA0q-LT.js (new) 6.44 kB 🔴 +6.44 kB 🔴 +2.12 kB 🔴 +1.89 kB
assets/UserPanel-PafY2gaU.js (removed) 6.16 kB 🟢 -6.16 kB 🟢 -1.99 kB 🟢 -1.75 kB
assets/UserPanel-C7V10qE2.js (new) 6.16 kB 🔴 +6.16 kB 🔴 +1.99 kB 🔴 +1.76 kB
assets/cloudRemoteConfig-CTdcemw2.js (removed) 1.44 kB 🟢 -1.44 kB 🟢 -708 B 🟢 -615 B
assets/cloudRemoteConfig-BjART4Vk.js (new) 1.44 kB 🔴 +1.44 kB 🔴 +703 B 🔴 +612 B
assets/refreshRemoteConfig-5ZU2vCMc.js (new) 1.14 kB 🔴 +1.14 kB 🔴 +524 B 🔴 +457 B
assets/refreshRemoteConfig-DjEge2O4.js (removed) 1.14 kB 🟢 -1.14 kB 🟢 -523 B 🟢 -459 B
assets/config-QxkqTZy6.js 996 B 996 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-3cK4vYSX.js 27.9 kB 27.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-AqJa7Oe1.js 28.7 kB 28.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BOcWl0Qp.js 34.2 kB 34.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-BodhSOuG.js 30.5 kB 30.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CCD8qxmc.js 27.8 kB 27.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-CVDNuOXV.js 23.9 kB 23.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DLodCRRz.js 24.5 kB 24.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DLqeCT09.js 38.5 kB 38.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DP-OgTXN.js 29.9 kB 29.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-DRIXyZ_Z.js 28.8 kB 28.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/settings-IRk9rDbu.js 32.4 kB 32.4 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 10 added / 10 removed

User & Accounts — 16 kB (baseline 16 kB) • 🟢 -4 B

Authentication, profile, and account management bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/PasswordFields-CVWWQ8UT.js (new) 4.51 kB 🔴 +4.51 kB 🔴 +1.36 kB 🔴 +1.2 kB
assets/PasswordFields-CwRSeTaU.js (removed) 4.51 kB 🟢 -4.51 kB 🟢 -1.36 kB 🟢 -1.2 kB
assets/auth-CZa7JLyJ.js (removed) 3.4 kB 🟢 -3.4 kB 🟢 -1.18 kB 🟢 -995 B
assets/auth-yoqD1Ox2.js (new) 3.4 kB 🔴 +3.4 kB 🔴 +1.18 kB 🔴 +988 B
assets/SignUpForm-CH2dkmJA.js (new) 3.01 kB 🔴 +3.01 kB 🔴 +1.23 kB 🔴 +1.1 kB
assets/SignUpForm-PavT9vsi.js (removed) 3.01 kB 🟢 -3.01 kB 🟢 -1.23 kB 🟢 -1.09 kB
assets/UpdatePasswordContent-D-qbuHN8.js (removed) 2.37 kB 🟢 -2.37 kB 🟢 -1.07 kB 🟢 -942 B
assets/UpdatePasswordContent-Dm0FjO_7.js (new) 2.37 kB 🔴 +2.37 kB 🔴 +1.06 kB 🔴 +939 B
assets/firebaseAuthStore-kxQMiZ9w.js (removed) 790 B 🟢 -790 B 🟢 -388 B 🟢 -380 B
assets/firebaseAuthStore-L4KMzjMn.js (new) 788 B 🔴 +788 B 🔴 +384 B 🔴 +344 B
assets/auth-DeNTFOjW.js (removed) 357 B 🟢 -357 B 🟢 -223 B 🟢 -212 B
assets/auth-qvoDgWMT.js (new) 357 B 🔴 +357 B 🔴 +223 B 🔴 +193 B
assets/WorkspaceProfilePic-Cv4HUNOU.js 1.57 kB 1.57 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 6 added / 6 removed

Editors & Dialogs — 736 B (baseline 738 B) • 🟢 -2 B

Modals, dialogs, drawers, and in-app editors

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useSubscriptionDialog-Bghzb7G7.js (removed) 738 B 🟢 -738 B 🟢 -378 B 🟢 -351 B
assets/useSubscriptionDialog-Gg6t9Zoj.js (new) 736 B 🔴 +736 B 🔴 +376 B 🔴 +326 B

Status: 1 added / 1 removed

UI Components — 46.9 kB (baseline 47 kB) • 🟢 -7 B

Reusable component library chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useTerminalTabs-DDvxyES0.js (removed) 9.85 kB 🟢 -9.85 kB 🟢 -3.4 kB 🟢 -2.99 kB
assets/useTerminalTabs-xIV536am.js (new) 9.84 kB 🔴 +9.84 kB 🔴 +3.4 kB 🔴 +2.99 kB
assets/ComfyQueueButton-D709DRzC.js (removed) 8.02 kB 🟢 -8.02 kB 🟢 -2.49 kB 🟢 -2.23 kB
assets/ComfyQueueButton-CESkMjZ9.js (new) 8.02 kB 🔴 +8.02 kB 🔴 +2.49 kB 🔴 +2.22 kB
assets/TopbarBadge-BG-5K9Vv.js (removed) 7.45 kB 🟢 -7.45 kB 🟢 -1.81 kB 🟢 -1.6 kB
assets/TopbarBadge-CNZvjjLw.js (new) 7.45 kB 🔴 +7.45 kB 🔴 +1.81 kB 🔴 +1.6 kB
assets/ScrubableNumberInput-BFZyZfVz.js (removed) 5.94 kB 🟢 -5.94 kB 🟢 -2.06 kB 🟢 -1.83 kB
assets/ScrubableNumberInput-CVilsRHA.js (new) 5.94 kB 🔴 +5.94 kB 🔴 +2.06 kB 🔴 +1.83 kB
assets/FormSearchInput-DA66ft_f.js (new) 3.73 kB 🔴 +3.73 kB 🔴 +1.54 kB 🔴 +1.35 kB
assets/FormSearchInput-DF1KXWVW.js (removed) 3.73 kB 🟢 -3.73 kB 🟢 -1.55 kB 🟢 -1.36 kB
assets/Button-_rljyWPY.js (new) 2.98 kB 🔴 +2.98 kB 🔴 +1.2 kB 🔴 +1.05 kB
assets/Button-UMWODG-q.js (removed) 2.98 kB 🟢 -2.98 kB 🟢 -1.2 kB 🟢 -1.05 kB
assets/SubscribeButton-_HK8_ViA.js (new) 2.35 kB 🔴 +2.35 kB 🔴 +1.02 kB 🔴 +889 B
assets/SubscribeButton-mitfujBm.js (removed) 2.35 kB 🟢 -2.35 kB 🟢 -1.02 kB 🟢 -890 B
assets/WidgetButton-CJqDWz7I.js (new) 1.84 kB 🔴 +1.84 kB 🔴 +878 B 🔴 +794 B
assets/WidgetButton-hy7QYdVc.js (removed) 1.84 kB 🟢 -1.84 kB 🟢 -878 B 🟢 -779 B
assets/cloudFeedbackTopbarButton-C24tWwdl.js (removed) 1.6 kB 🟢 -1.6 kB 🟢 -856 B 🟢 -762 B
assets/cloudFeedbackTopbarButton-_3iB19XV.js (new) 1.59 kB 🔴 +1.59 kB 🔴 +852 B 🔴 +760 B
assets/CloudBadge-B8O5mK9a.js (new) 1.24 kB 🔴 +1.24 kB 🔴 +608 B 🔴 +525 B
assets/CloudBadge-Vch9A557.js (removed) 1.24 kB 🟢 -1.24 kB 🟢 -609 B 🟢 -525 B
assets/UserAvatar-BRD5QYeZ.js (new) 1.17 kB 🔴 +1.17 kB 🔴 +616 B 🔴 +525 B
assets/UserAvatar-DcdcWs-e.js (removed) 1.17 kB 🟢 -1.17 kB 🟢 -615 B 🟢 -525 B
assets/ComfyQueueButton-Do7eFV0t.js (removed) 795 B 🟢 -795 B 🟢 -394 B 🟢 -352 B
assets/ComfyQueueButton-BEJ8CeB6.js (new) 793 B 🔴 +793 B 🔴 +390 B 🔴 +349 B

Status: 12 added / 12 removed

Data & Services — 2.54 MB (baseline 2.52 MB) • 🔴 +19.1 kB

Stores, services, APIs, and repositories

File Before After Δ Raw Δ Gzip Δ Brotli
assets/dialogService-DkTsyskA.js (removed) 1.73 MB 🟢 -1.73 MB 🟢 -388 kB 🟢 -294 kB
assets/dialogService-MkwXlDNu.js (new) 1.73 MB 🔴 +1.73 MB 🔴 +389 kB 🔴 +294 kB
assets/api-DPF8WPFZ.js (new) 675 kB 🔴 +675 kB 🔴 +153 kB 🔴 +121 kB
assets/api-TD9eYUMq.js (removed) 654 kB 🟢 -654 kB 🟢 -148 kB 🟢 -118 kB
assets/load3dService-BEKsfQWz.js (removed) 91 kB 🟢 -91 kB 🟢 -19.1 kB 🟢 -16.4 kB
assets/load3dService-BtUZYpZ3.js (new) 91 kB 🔴 +91 kB 🔴 +19.1 kB 🔴 +16.4 kB
assets/systemStatsStore-B7t4ZJef.js (removed) 12.7 kB 🟢 -12.7 kB 🟢 -4.42 kB 🟢 -3.88 kB
assets/extensionStore-CfLFqi6t.js (new) 12.7 kB 🔴 +12.7 kB 🔴 +4.42 kB 🔴 +3.88 kB
assets/releaseStore-BYOtru2b.js (removed) 7.96 kB 🟢 -7.96 kB 🟢 -2.22 kB 🟢 -1.95 kB
assets/releaseStore-B3-OqijZ.js (new) 7.96 kB 🔴 +7.96 kB 🔴 +2.22 kB 🔴 +1.95 kB
assets/keybindingService-CUzi1RTB.js (new) 6.52 kB 🔴 +6.52 kB 🔴 +1.71 kB 🔴 +1.48 kB
assets/keybindingService-Ly637AbQ.js (removed) 6.52 kB 🟢 -6.52 kB 🟢 -1.7 kB 🟢 -1.47 kB
assets/bootstrapStore-B34C3y7v.js (new) 2.08 kB 🔴 +2.08 kB 🔴 +873 B 🔴 +793 B
assets/bootstrapStore-C-lZ5Iuz.js (removed) 2.08 kB 🟢 -2.08 kB 🟢 -869 B 🟢 -789 B
assets/userStore-Dd6s1pFz.js (removed) 1.85 kB 🟢 -1.85 kB 🟢 -721 B 🟢 -670 B
assets/userStore-klZUONWQ.js (new) 1.85 kB 🔴 +1.85 kB 🔴 +722 B 🔴 +675 B
assets/audioService-7DEh5l8i.js (removed) 1.73 kB 🟢 -1.73 kB 🟢 -849 B 🟢 -725 B
assets/audioService-DdbwrA0_.js (new) 1.73 kB 🔴 +1.73 kB 🔴 +849 B 🔴 +726 B
assets/releaseStore-BEXHEAFp.js (removed) 762 B 🟢 -762 B 🟢 -385 B 🟢 -339 B
assets/releaseStore-Cf9FbHdW.js (new) 760 B 🔴 +760 B 🔴 +381 B 🔴 +341 B
assets/settingStore-CMl-qkbu.js (removed) 746 B 🟢 -746 B 🟢 -386 B 🟢 -345 B
assets/settingStore-DsammtlH.js (new) 744 B 🔴 +744 B 🔴 +383 B 🔴 +343 B
assets/workflowDraftStore-C6rXFb3D.js (removed) 738 B 🟢 -738 B 🟢 -378 B 🟢 -338 B
assets/workflowDraftStore-CydT7yFb.js (new) 736 B 🔴 +736 B 🔴 +373 B 🔴 +334 B
assets/dialogService-D3FgPnNO.js (removed) 727 B 🟢 -727 B 🟢 -365 B 🟢 -326 B
assets/dialogService-B4Z_Gmlj.js (new) 725 B 🔴 +725 B 🔴 +365 B 🔴 +327 B
assets/serverConfigStore-DEs1_NHN.js 2.32 kB 2.32 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 13 added / 13 removed

Utilities & Hooks — 58.3 kB (baseline 58.3 kB) • 🟢 -7 B

Helpers, composables, and utility bundles

File Before After Δ Raw Δ Gzip Δ Brotli
assets/useLoad3d-D6bfYSrX.js (new) 14.6 kB 🔴 +14.6 kB 🔴 +3.63 kB 🔴 +3.2 kB
assets/useLoad3d-r2-WFcJA.js (removed) 14.6 kB 🟢 -14.6 kB 🟢 -3.63 kB 🟢 -3.21 kB
assets/useLoad3dViewer-Bmj07NKT.js (new) 14.1 kB 🔴 +14.1 kB 🔴 +3.15 kB 🔴 +2.79 kB
assets/useLoad3dViewer-Dc88wiJM.js (removed) 14.1 kB 🟢 -14.1 kB 🟢 -3.15 kB 🟢 -2.8 kB
assets/colorUtil-C2D2Hl5T.js (new) 7 kB 🔴 +7 kB 🔴 +2.14 kB 🔴 +1.9 kB
assets/colorUtil-DGTUhmp-.js (removed) 7 kB 🟢 -7 kB 🟢 -2.14 kB 🟢 -1.9 kB
assets/useFeatureFlags-C78KZtWS.js (removed) 4.14 kB 🟢 -4.14 kB 🟢 -1.24 kB 🟢 -1.05 kB
assets/useFeatureFlags-DwVL6B9H.js (new) 4.14 kB 🔴 +4.14 kB 🔴 +1.24 kB 🔴 +1.06 kB
assets/useWorkspaceUI-CX5tKG0r.js (removed) 3 kB 🟢 -3 kB 🟢 -822 B 🟢 -732 B
assets/useWorkspaceUI-DbXpjdla.js (new) 3 kB 🔴 +3 kB 🔴 +821 B 🔴 +700 B
assets/useSubscriptionCredits-B9KxUhJ9.js (removed) 2.75 kB 🟢 -2.75 kB 🟢 -1.04 kB 🟢 -899 B
assets/useSubscriptionCredits-Bi0Lu0VP.js (new) 2.75 kB 🔴 +2.75 kB 🔴 +1.03 kB 🔴 +898 B
assets/subscriptionCheckoutUtil-B6Vxs9wB.js (removed) 2.53 kB 🟢 -2.53 kB 🟢 -1.06 kB 🟢 -957 B
assets/subscriptionCheckoutUtil-C7TvBW0p.js (new) 2.53 kB 🔴 +2.53 kB 🔴 +1.06 kB 🔴 +952 B
assets/useExternalLink-C002nTPZ.js (new) 1.66 kB 🔴 +1.66 kB 🔴 +771 B 🔴 +678 B
assets/useExternalLink-CcmSiVP7.js (removed) 1.66 kB 🟢 -1.66 kB 🟢 -770 B 🟢 -676 B
assets/markdownRendererUtil-Cddas8Zl.js (new) 1.56 kB 🔴 +1.56 kB 🔴 +813 B 🔴 +696 B
assets/markdownRendererUtil-DOdPeMQc.js (removed) 1.56 kB 🟢 -1.56 kB 🟢 -814 B 🟢 -700 B
assets/useErrorHandling-CkzntZ9d.js (removed) 1.5 kB 🟢 -1.5 kB 🟢 -629 B 🟢 -535 B
assets/useErrorHandling-D6R6hSpQ.js (new) 1.5 kB 🔴 +1.5 kB 🔴 +630 B 🔴 +536 B
assets/useWorkspaceSwitch-5Ry4D8jX.js (removed) 1.25 kB 🟢 -1.25 kB 🟢 -545 B 🟢 -481 B
assets/useWorkspaceSwitch-TLjyRcdt.js (new) 1.25 kB 🔴 +1.25 kB 🔴 +544 B 🔴 +477 B
assets/useLoad3d-Dh6D7rWr.js (removed) 861 B 🟢 -861 B 🟢 -423 B 🟢 -382 B
assets/useLoad3d-AG2rbeUB.js (new) 859 B 🔴 +859 B 🔴 +421 B 🔴 +384 B
assets/audioUtils-CfFkPn6Y.js (new) 858 B 🔴 +858 B 🔴 +500 B 🔴 +402 B
assets/audioUtils-DYzyCaec.js (removed) 858 B 🟢 -858 B 🟢 -500 B 🟢 -404 B
assets/useLoad3dViewer-DxuJaPX2.js (removed) 840 B 🟢 -840 B 🟢 -410 B 🟢 -373 B
assets/useLoad3dViewer-DHWSOAxv.js (new) 838 B 🔴 +838 B 🔴 +408 B 🔴 +372 B
assets/useCurrentUser-BQtCTfF3.js (removed) 724 B 🟢 -724 B 🟢 -369 B 🟢 -331 B
assets/useCurrentUser-W8iBk7TG.js (new) 722 B 🔴 +722 B 🔴 +371 B 🔴 +329 B
assets/envUtil-BQSmRN2Q.js (removed) 466 B 🟢 -466 B 🟢 -294 B 🟢 -249 B
assets/envUtil-Clzmwvt4.js (new) 466 B 🔴 +466 B 🔴 +293 B 🔴 +251 B
assets/_plugin-vue_export-helper-c0As6Sx3.js (new) 315 B 🔴 +315 B 🔴 +232 B 🔴 +201 B
assets/_plugin-vue_export-helper-CY4XIWDa.js (removed) 315 B 🟢 -315 B 🟢 -232 B 🟢 -201 B
assets/SkeletonUtils-BputJAFn.js 133 B 133 B ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 17 added / 17 removed

Vendor & Third-Party — 8.84 MB (baseline 8.84 MB) • 🔴 +389 B

External libraries and shared vendor chunks

File Before After Δ Raw Δ Gzip Δ Brotli
assets/vendor-primevue-D4sztoo6.js (removed) 1.73 MB 🟢 -1.73 MB 🟢 -311 kB 🟢 -190 kB
assets/vendor-primevue-DCruF9hs.js (new) 1.73 MB 🔴 +1.73 MB 🔴 +311 kB 🔴 +190 kB
assets/vendor-other-Cj6OpvOm.js (new) 1.52 MB 🔴 +1.52 MB 🔴 +319 kB 🔴 +254 kB
assets/vendor-other-LUzoxniz.js (removed) 1.52 MB 🟢 -1.52 MB 🟢 -319 kB 🟢 -254 kB
assets/vendor-tiptap-BnYkbQDM.js (new) 634 kB 🔴 +634 kB 🔴 +149 kB 🔴 +120 kB
assets/vendor-tiptap-CHaNo_rA.js (removed) 634 kB 🟢 -634 kB 🟢 -149 kB 🟢 -120 kB
assets/vendor-axios-Cp6hch1I.js 70.7 kB 70.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-chart-BxkFiWzp.js 399 kB 399 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-firebase-BvMr43CG.js 836 kB 836 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-i18n-DNX73mqE.js 133 kB 133 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-markdown-D5S6AC80.js 103 kB 103 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-reka-ui-DVmi2O2Z.js 388 kB 388 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-LBLOE6BD.js 1.8 MB 1.8 MB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vue-core-DtiQ1dr9.js 311 kB 311 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-vueuse-D2jVNnmE.js 113 kB 113 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/vendor-xterm-vkxZGffR.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: 3 added / 3 removed

Other — 7.62 MB (baseline 7.62 MB) • 🔴 +420 B

Bundles that do not match a named category

File Before After Δ Raw Δ Gzip Δ Brotli
assets/i18n-CPASsDRs.js (new) 521 kB 🔴 +521 kB 🔴 +100 kB 🔴 +78.1 kB
assets/i18n-DmnrqQYS.js (removed) 521 kB 🟢 -521 kB 🟢 -100 kB 🟢 -78 kB
assets/core-fmAUDKzO.js (new) 73 kB 🔴 +73 kB 🔴 +18.8 kB 🔴 +16.1 kB
assets/core-BKR3ZZck.js (removed) 72.8 kB 🟢 -72.8 kB 🟢 -18.8 kB 🟢 -16.1 kB
assets/groupNode-BrdIqhsg.js (new) 71.8 kB 🔴 +71.8 kB 🔴 +17.7 kB 🔴 +15.5 kB
assets/groupNode-V2sC0hj5.js (removed) 71.8 kB 🟢 -71.8 kB 🟢 -17.7 kB 🟢 -15.5 kB
assets/WidgetSelect-ixYufF5C.js (removed) 58.1 kB 🟢 -58.1 kB 🟢 -12.4 kB 🟢 -10.7 kB
assets/WidgetSelect-Cl9Rj_zD.js (new) 58.1 kB 🔴 +58.1 kB 🔴 +12.4 kB 🔴 +10.7 kB
assets/SubscriptionRequiredDialogContentWorkspace-BZK_k7ET.js (removed) 45.8 kB 🟢 -45.8 kB 🟢 -8.56 kB 🟢 -7.4 kB
assets/SubscriptionRequiredDialogContentWorkspace-5CHhmM8o.js (new) 45.8 kB 🔴 +45.8 kB 🔴 +8.56 kB 🔴 +7.41 kB
assets/Load3DControls-DDX1zYCW.js (removed) 30.9 kB 🟢 -30.9 kB 🟢 -5.34 kB 🟢 -4.64 kB
assets/Load3DControls-DWLeSmRF.js (new) 30.9 kB 🔴 +30.9 kB 🔴 +5.34 kB 🔴 +4.64 kB
assets/WorkspacePanelContent-5RfRZB4R.js (removed) 29.3 kB 🟢 -29.3 kB 🟢 -6.14 kB 🟢 -5.39 kB
assets/WorkspacePanelContent-CSi_MX7Q.js (new) 29.3 kB 🔴 +29.3 kB 🔴 +6.14 kB 🔴 +5.38 kB
assets/SubscriptionRequiredDialogContent-DdHo3twG.js (removed) 26.2 kB 🟢 -26.2 kB 🟢 -6.56 kB 🟢 -5.78 kB
assets/SubscriptionRequiredDialogContent-CZVlUaOa.js (new) 26.2 kB 🔴 +26.2 kB 🔴 +6.56 kB 🔴 +5.78 kB
assets/Load3dViewerContent-CCXraRhP.js (new) 23 kB 🔴 +23 kB 🔴 +5.18 kB 🔴 +4.5 kB
assets/Load3dViewerContent-DiYPTw8e.js (removed) 23 kB 🟢 -23 kB 🟢 -5.18 kB 🟢 -4.49 kB
assets/WidgetImageCrop-DrBswB4H.js (removed) 22.1 kB 🟢 -22.1 kB 🟢 -5.51 kB 🟢 -4.85 kB
assets/WidgetImageCrop-Drtv2vOo.js (new) 22.1 kB 🔴 +22.1 kB 🔴 +5.5 kB 🔴 +4.85 kB
assets/SubscriptionPanelContentWorkspace-BhndMxqo.js (new) 21.6 kB 🔴 +21.6 kB 🔴 +5.02 kB 🔴 +4.43 kB
assets/SubscriptionPanelContentWorkspace-nN_lavAX.js (removed) 21.6 kB 🟢 -21.6 kB 🟢 -5.02 kB 🟢 -4.43 kB
assets/CurrentUserPopoverWorkspace-Bif34PgB.js (removed) 19.8 kB 🟢 -19.8 kB 🟢 -4.86 kB 🟢 -4.33 kB
assets/CurrentUserPopoverWorkspace-Da3y2eFG.js (new) 19.8 kB 🔴 +19.8 kB 🔴 +4.86 kB 🔴 +4.33 kB
assets/SignInContent-4gRaeXhH.js (removed) 18.9 kB 🟢 -18.9 kB 🟢 -4.78 kB 🟢 -4.18 kB
assets/SignInContent-DRLQeCEV.js (new) 18.9 kB 🔴 +18.9 kB 🔴 +4.79 kB 🔴 +4.18 kB
assets/WidgetInputNumber-B8uilaK5.js (new) 18.7 kB 🔴 +18.7 kB 🔴 +4.75 kB 🔴 +4.21 kB
assets/WidgetInputNumber-Mrg1iXOM.js (removed) 18.7 kB 🟢 -18.7 kB 🟢 -4.75 kB 🟢 -4.22 kB
assets/WidgetRecordAudio-iGY7knPB.js (removed) 17.3 kB 🟢 -17.3 kB 🟢 -4.94 kB 🟢 -4.42 kB
assets/WidgetRecordAudio-CAo7rbu4.js (new) 17.3 kB 🔴 +17.3 kB 🔴 +4.94 kB 🔴 +4.42 kB
assets/Load3D-BeyPCL-V.js (new) 16.2 kB 🔴 +16.2 kB 🔴 +4.03 kB 🔴 +3.52 kB
assets/Load3D-ZRJihGLR.js (removed) 16.2 kB 🟢 -16.2 kB 🟢 -4.03 kB 🟢 -3.51 kB
assets/load3d-CYF2cYnM.js (removed) 14.7 kB 🟢 -14.7 kB 🟢 -4.2 kB 🟢 -3.63 kB
assets/load3d-BVonJTVl.js (new) 14.7 kB 🔴 +14.7 kB 🔴 +4.19 kB 🔴 +3.63 kB
assets/AudioPreviewPlayer-Cg2dnUAr.js (removed) 10.9 kB 🟢 -10.9 kB 🟢 -3.19 kB 🟢 -2.84 kB
assets/AudioPreviewPlayer-CmdWcbQa.js (new) 10.9 kB 🔴 +10.9 kB 🔴 +3.19 kB 🔴 +2.87 kB
assets/changeTracker-Cwz1jGIl.js (new) 9.38 kB 🔴 +9.38 kB 🔴 +2.89 kB 🔴 +2.55 kB
assets/changeTracker-UHmw7eg8.js (removed) 9.38 kB 🟢 -9.38 kB 🟢 -2.89 kB 🟢 -2.54 kB
assets/nodeTemplates-xgjmVIMS.js (removed) 9.3 kB 🟢 -9.3 kB 🟢 -3.26 kB 🟢 -2.86 kB
assets/nodeTemplates-7Alq916e.js (new) 9.29 kB 🔴 +9.29 kB 🔴 +3.25 kB 🔴 +2.86 kB
assets/SelectValue-D-D55K_n.js (new) 8.94 kB 🔴 +8.94 kB 🔴 +2.27 kB 🔴 +2 kB
assets/SelectValue-DHhmuu6k.js (removed) 8.94 kB 🟢 -8.94 kB 🟢 -2.27 kB 🟢 -2 kB
assets/InviteMemberDialogContent-CPhLnAxd.js (removed) 7.38 kB 🟢 -7.38 kB 🟢 -2.29 kB 🟢 -2.01 kB
assets/InviteMemberDialogContent-BC4J3RnH.js (new) 7.38 kB 🔴 +7.38 kB 🔴 +2.29 kB 🔴 +2 kB
assets/WidgetToggleSwitch-Bjuvinl9.js (removed) 6.8 kB 🟢 -6.8 kB 🟢 -2.19 kB 🟢 -1.94 kB
assets/WidgetToggleSwitch-C1n8cLaU.js (new) 6.8 kB 🔴 +6.8 kB 🔴 +2.19 kB 🔴 +1.94 kB
assets/Load3DConfiguration-Dx94uz6D.js (removed) 6.27 kB 🟢 -6.27 kB 🟢 -1.92 kB 🟢 -1.68 kB
assets/Load3DConfiguration-FaOXwfY4.js (new) 6.27 kB 🔴 +6.27 kB 🔴 +1.91 kB 🔴 +1.68 kB
assets/CreateWorkspaceDialogContent-D5gMnqSR.js (removed) 5.53 kB 🟢 -5.53 kB 🟢 -1.99 kB 🟢 -1.74 kB
assets/CreateWorkspaceDialogContent-Dt7yxXSI.js (new) 5.53 kB 🔴 +5.53 kB 🔴 +1.99 kB 🔴 +1.73 kB
assets/onboardingCloudRoutes-CF4cRBEA.js (removed) 5.41 kB 🟢 -5.41 kB 🟢 -1.83 kB 🟢 -1.59 kB
assets/onboardingCloudRoutes-C30plPdj.js (new) 5.41 kB 🔴 +5.41 kB 🔴 +1.83 kB 🔴 +1.6 kB
assets/EditWorkspaceDialogContent-KvYYTXiP.js (removed) 5.33 kB 🟢 -5.33 kB 🟢 -1.95 kB 🟢 -1.7 kB
assets/EditWorkspaceDialogContent-Cfrqzghv.js (new) 5.33 kB 🔴 +5.33 kB 🔴 +1.94 kB 🔴 +1.69 kB
assets/ValueControlPopover-DuoQ6K1Q.js (removed) 4.92 kB 🟢 -4.92 kB 🟢 -1.76 kB 🟢 -1.58 kB
assets/ValueControlPopover-DK9ikFUp.js (new) 4.92 kB 🔴 +4.92 kB 🔴 +1.76 kB 🔴 +1.57 kB
assets/Preview3d-D3r4rV4p.js (removed) 4.82 kB 🟢 -4.82 kB 🟢 -1.56 kB 🟢 -1.36 kB
assets/Preview3d-UpNX3ZIz.js (new) 4.81 kB 🔴 +4.81 kB 🔴 +1.56 kB 🔴 +1.37 kB
assets/CancelSubscriptionDialogContent-Dz-i5ikj.js (removed) 4.79 kB 🟢 -4.79 kB 🟢 -1.78 kB 🟢 -1.56 kB
assets/CancelSubscriptionDialogContent-GKV5TN-4.js (new) 4.79 kB 🔴 +4.79 kB 🔴 +1.78 kB 🔴 +1.56 kB
assets/AnimationControls-DjijCZ7o.js (new) 4.61 kB 🔴 +4.61 kB 🔴 +1.6 kB 🔴 +1.41 kB
assets/AnimationControls-pnyHXmeA.js (removed) 4.61 kB 🟢 -4.61 kB 🟢 -1.6 kB 🟢 -1.4 kB
assets/DeleteWorkspaceDialogContent-C3_ARZ6b.js (removed) 4.23 kB 🟢 -4.23 kB 🟢 -1.63 kB 🟢 -1.43 kB
assets/DeleteWorkspaceDialogContent-D8ypQZuz.js (new) 4.23 kB 🔴 +4.23 kB 🔴 +1.63 kB 🔴 +1.42 kB
assets/WidgetWithControl-BFmNDh6c.js (removed) 4.11 kB 🟢 -4.11 kB 🟢 -1.77 kB 🟢 -1.59 kB
assets/WidgetWithControl-CYdkBmXS.js (new) 4.1 kB 🔴 +4.1 kB 🔴 +1.78 kB 🔴 +1.61 kB
assets/LeaveWorkspaceDialogContent-BwMAVsm7.js (removed) 4.06 kB 🟢 -4.06 kB 🟢 -1.57 kB 🟢 -1.37 kB
assets/LeaveWorkspaceDialogContent-CMRQpVSs.js (new) 4.06 kB 🔴 +4.06 kB 🔴 +1.58 kB 🔴 +1.37 kB
assets/RemoveMemberDialogContent-DL6PhcNp.js (removed) 4.04 kB 🟢 -4.04 kB 🟢 -1.52 kB 🟢 -1.33 kB
assets/RemoveMemberDialogContent-ZlAX1I4x.js (new) 4.04 kB 🔴 +4.04 kB 🔴 +1.52 kB 🔴 +1.33 kB
assets/WidgetTextarea-BekkBcLd.js (removed) 3.96 kB 🟢 -3.96 kB 🟢 -1.49 kB 🟢 -1.3 kB
assets/WidgetTextarea-D8GUT4SH.js (new) 3.96 kB 🔴 +3.96 kB 🔴 +1.49 kB 🔴 +1.31 kB
assets/RevokeInviteDialogContent-BCytb44D.js (removed) 3.96 kB 🟢 -3.96 kB 🟢 -1.54 kB 🟢 -1.35 kB
assets/RevokeInviteDialogContent-CGY3lJxE.js (new) 3.95 kB 🔴 +3.95 kB 🔴 +1.54 kB 🔴 +1.36 kB
assets/InviteMemberUpsellDialogContent-Snv0IqgB.js (removed) 3.83 kB 🟢 -3.83 kB 🟢 -1.4 kB 🟢 -1.23 kB
assets/InviteMemberUpsellDialogContent-CjNP3mII.js (new) 3.82 kB 🔴 +3.82 kB 🔴 +1.4 kB 🔴 +1.23 kB
assets/Popover-BRmRgwNy.js (new) 3.65 kB 🔴 +3.65 kB 🔴 +1.43 kB 🔴 +1.26 kB
assets/Popover-Du8xr1yg.js (removed) 3.65 kB 🟢 -3.65 kB 🟢 -1.43 kB 🟢 -1.26 kB
assets/WidgetGalleria-D6GhU3q2.js (new) 3.61 kB 🔴 +3.61 kB 🔴 +1.39 kB 🔴 +1.25 kB
assets/WidgetGalleria-DFOYH-wE.js (removed) 3.61 kB 🟢 -3.61 kB 🟢 -1.4 kB 🟢 -1.25 kB
assets/Slider-BilMNRAB.js (removed) 3.52 kB 🟢 -3.52 kB 🟢 -1.36 kB 🟢 -1.19 kB
assets/Slider-Cv2r2AJm.js (new) 3.52 kB 🔴 +3.52 kB 🔴 +1.36 kB 🔴 +1.18 kB
assets/saveMesh-CWTENjpR.js (removed) 3.38 kB 🟢 -3.38 kB 🟢 -1.46 kB 🟢 -1.29 kB
assets/saveMesh-C0gRGpre.js (new) 3.38 kB 🔴 +3.38 kB 🔴 +1.45 kB 🔴 +1.29 kB
assets/WidgetBoundingBox-6TJd03tq.js (removed) 3.19 kB 🟢 -3.19 kB 🟢 -895 B 🟢 -777 B
assets/WidgetBoundingBox-B1dc4Mvs.js (new) 3.19 kB 🔴 +3.19 kB 🔴 +892 B 🔴 +780 B
assets/cloudSessionCookie-rbOHq3d9.js (removed) 3.1 kB 🟢 -3.1 kB 🟢 -1.09 kB 🟢 -960 B
assets/cloudSessionCookie-DQ1OsSpN.js (new) 3.1 kB 🔴 +3.1 kB 🔴 +1.08 kB 🔴 +944 B
assets/WidgetMarkdown-BLTsLNPD.js (new) 2.93 kB 🔴 +2.93 kB 🔴 +1.23 kB 🔴 +1.08 kB
assets/WidgetMarkdown-Cqk0nyRJ.js (removed) 2.93 kB 🟢 -2.93 kB 🟢 -1.23 kB 🟢 -1.07 kB
assets/GlobalToast-BA0xOBHr.js (removed) 2.91 kB 🟢 -2.91 kB 🟢 -1.21 kB 🟢 -1.03 kB
assets/GlobalToast-CssTfo-G.js (new) 2.91 kB 🔴 +2.91 kB 🔴 +1.21 kB 🔴 +1.03 kB
assets/WidgetColorPicker-BT0--RBS.js (removed) 2.9 kB 🟢 -2.9 kB 🟢 -1.23 kB 🟢 -1.1 kB
assets/WidgetColorPicker-C2UPaFjl.js (new) 2.9 kB 🔴 +2.9 kB 🔴 +1.23 kB 🔴 +1.1 kB
assets/MediaVideoTop-3eH_a2Z8.js (removed) 2.77 kB 🟢 -2.77 kB 🟢 -1.13 kB 🟢 -992 B
assets/MediaVideoTop-BO2aZavV.js (new) 2.77 kB 🔴 +2.77 kB 🔴 +1.13 kB 🔴 +999 B
assets/ApiNodesSignInContent-CuPvGXL1.js (new) 2.69 kB 🔴 +2.69 kB 🔴 +1.05 kB 🔴 +920 B
assets/ApiNodesSignInContent-D3WDGfAG.js (removed) 2.69 kB 🟢 -2.69 kB 🟢 -1.05 kB 🟢 -922 B
assets/WidgetChart-CgT9e03M.js (new) 2.21 kB 🔴 +2.21 kB 🔴 +951 B 🔴 +821 B
assets/WidgetChart-crz6xOcB.js (removed) 2.21 kB 🟢 -2.21 kB 🟢 -951 B 🟢 -820 B
assets/SubscribeToRun-CvYnagSB.js (new) 2.2 kB 🔴 +2.2 kB 🔴 +1.01 kB 🔴 +881 B
assets/SubscribeToRun-CwUNcbty.js (removed) 2.2 kB 🟢 -2.2 kB 🟢 -1.01 kB 🟢 -881 B
assets/WidgetLayoutField-BGDGIZQQ.js (removed) 1.98 kB 🟢 -1.98 kB 🟢 -894 B 🟢 -773 B
assets/WidgetLayoutField-DJYJrhiu.js (new) 1.98 kB 🔴 +1.98 kB 🔴 +891 B 🔴 +778 B
assets/WidgetInputText-Br3WkRHc.js (removed) 1.86 kB 🟢 -1.86 kB 🟢 -872 B 🟢 -789 B
assets/WidgetInputText-DHQ3B5q-.js (new) 1.86 kB 🔴 +1.86 kB 🔴 +872 B 🔴 +782 B
assets/BaseViewTemplate-Bn_mihBn.js (removed) 1.78 kB 🟢 -1.78 kB 🟢 -923 B 🟢 -833 B
assets/BaseViewTemplate-SpFD1rBU.js (new) 1.78 kB 🔴 +1.78 kB 🔴 +924 B 🔴 +810 B
assets/CloudRunButtonWrapper-BU5vylXH.js (removed) 1.68 kB 🟢 -1.68 kB 🟢 -785 B 🟢 -707 B
assets/CloudRunButtonWrapper-DIzeFfGk.js (new) 1.68 kB 🔴 +1.68 kB 🔴 +782 B 🔴 +717 B
assets/signInSchema-Bd1Doi2f.js (new) 1.53 kB 🔴 +1.53 kB 🔴 +562 B 🔴 +518 B
assets/signInSchema-CfwzEUHi.js (removed) 1.53 kB 🟢 -1.53 kB 🟢 -562 B 🟢 -516 B
assets/cloudBadges-BdQnsX_0.js (removed) 1.37 kB 🟢 -1.37 kB 🟢 -705 B 🟢 -620 B
assets/cloudBadges-CfuIiDI7.js (new) 1.37 kB 🔴 +1.37 kB 🔴 +699 B 🔴 +607 B
assets/VideoPlayOverlay-CMR8YZxL.js (new) 1.35 kB 🔴 +1.35 kB 🔴 +704 B 🔴 +622 B
assets/VideoPlayOverlay-DtqFG8Nu.js (removed) 1.35 kB 🟢 -1.35 kB 🟢 -703 B 🟢 -620 B
assets/cloudSubscription-Dt2D-8Tq.js (removed) 1.33 kB 🟢 -1.33 kB 🟢 -656 B 🟢 -570 B
assets/cloudSubscription-Czxfg7xu.js (new) 1.33 kB 🔴 +1.33 kB 🔴 +652 B 🔴 +569 B
assets/Load3D-CfDHomub.js (removed) 1.07 kB 🟢 -1.07 kB 🟢 -495 B 🟢 -444 B
assets/Load3D-BfsKCafF.js (new) 1.07 kB 🔴 +1.07 kB 🔴 +493 B 🔴 +442 B
assets/MediaOtherTop-B7AUGEAD.js (removed) 1.02 kB 🟢 -1.02 kB 🟢 -570 B 🟢 -475 B
assets/MediaOtherTop-BWV5s4M2.js (new) 1.02 kB 🔴 +1.02 kB 🔴 +569 B 🔴 +474 B
assets/MediaTextTop-BcCnQHMJ.js (new) 1.01 kB 🔴 +1.01 kB 🔴 +564 B 🔴 +475 B
assets/MediaTextTop-BWwMUA0Y.js (removed) 1.01 kB 🟢 -1.01 kB 🟢 -565 B 🟢 -476 B
assets/nightlyBadges-C9clwREL.js (removed) 1 kB 🟢 -1 kB 🟢 -535 B 🟢 -470 B
assets/nightlyBadges-Mr8AUUbX.js (new) 1 kB 🔴 +1 kB 🔴 +527 B 🔴 +465 B
assets/Load3dViewerContent-CWVkbb0b.js (removed) 995 B 🟢 -995 B 🟢 -464 B 🟢 -419 B
assets/Load3dViewerContent-Deaaeqah.js (new) 993 B 🔴 +993 B 🔴 +465 B 🔴 +412 B
assets/SubscriptionPanelContentWorkspace-BcEC4Mtn.js (removed) 932 B 🟢 -932 B 🟢 -438 B 🟢 -381 B
assets/SubscriptionPanelContentWorkspace-RoHzkRuV.js (new) 930 B 🔴 +930 B 🔴 +435 B 🔴 +381 B
assets/ComfyOrgHeader-Ce4UTXTl.js (removed) 910 B 🟢 -910 B 🟢 -496 B 🟢 -467 B
assets/ComfyOrgHeader-vsmXNmmw.js (new) 910 B 🔴 +910 B 🔴 +495 B 🔴 +426 B
assets/graphHasMissingNodes-8XZ8E7B8.js (new) 761 B 🔴 +761 B 🔴 +376 B 🔴 +331 B
assets/graphHasMissingNodes-GxhRskDw.js (removed) 761 B 🟢 -761 B 🟢 -373 B 🟢 -315 B
assets/changeTracker-C-YDZm__.js (removed) 759 B 🟢 -759 B 🟢 -384 B 🟢 -340 B
assets/changeTracker-B5o_hc9v.js (new) 757 B 🔴 +757 B 🔴 +383 B 🔴 +337 B
assets/WidgetLegacy-CArHXc6i.js (removed) 747 B 🟢 -747 B 🟢 -384 B 🟢 -354 B
assets/WidgetLegacy-txF8WQcd.js (new) 745 B 🔴 +745 B 🔴 +383 B 🔴 +338 B
assets/widget-NeEr3XWN.js (new) 586 B 🔴 +586 B 🔴 +362 B 🔴 +297 B
assets/WidgetInputNumber-VFz2M7hI.js (new) 469 B 🔴 +469 B 🔴 +260 B 🔴 +229 B
assets/WidgetInputNumber-YqczUm1x.js (removed) 469 B 🟢 -469 B 🟢 -263 B 🟢 -229 B
assets/widget-DTUjK0ZE.js (removed) 445 B 🟢 -445 B 🟢 -307 B 🟢 -249 B
assets/widgetTypes-B7_LakDW.js (removed) 393 B 🟢 -393 B 🟢 -259 B 🟢 -244 B
assets/widgetTypes-DjwSopYV.js (new) 393 B 🔴 +393 B 🔴 +258 B 🔴 +214 B
assets/WidgetBoundingBox-3ihy3W8s.js (new) 283 B 🔴 +283 B 🔴 +185 B 🔴 +164 B
assets/WidgetBoundingBox-m1G5pcig.js (removed) 283 B 🟢 -283 B 🟢 -185 B 🟢 -162 B
assets/src-D4w3iVDe.js (removed) 251 B 🟢 -251 B 🟢 -209 B 🟢 -180 B
assets/src-PwE2kea4.js (new) 251 B 🔴 +251 B 🔴 +210 B 🔴 +183 B
assets/i18n-BSCphJtf.js (new) 199 B 🔴 +199 B 🔴 +159 B 🔴 +137 B
assets/i18n-Dj4pg-qT.js (removed) 199 B 🟢 -199 B 🟢 -160 B 🟢 -139 B
assets/auto-BTnZwrs2.js 1.7 kB 1.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/comfy-logo-single-DhnNuB-i.js 198 B 198 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BIWmUVEc.js 16.9 kB 16.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-BOt38VCw.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-C0NcxRH3.js 18.8 kB 18.8 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-C2xuXGb5.js 17.5 kB 17.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-CmiKIQwc.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DMwP8S5e.js 16.7 kB 16.7 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-DwBt3HhT.js 15.9 kB 15.9 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-JX559A2n.js 15.1 kB 15.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-n_2KJWcD.js 15.2 kB 15.2 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-OO-8nFm9.js 16.1 kB 16.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/commands-wq2Y-YKn.js 17.5 kB 17.5 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/constants-BIWjk1ar.js 579 B 579 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BgWDQhKn.js 145 kB 145 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-BH99zfen.js 148 kB 148 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-Cl0TDHs-.js 143 kB 143 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CTgQOf6T.js 196 kB 196 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CTIe-PdP.js 140 kB 140 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-CxmFjxCA.js 176 kB 176 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-D_siPGTl.js 162 kB 162 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-D-U7Lzb5.js 170 kB 170 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-d6c2x81N.js 123 kB 123 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DN0hguJt.js 141 kB 141 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/main-DzJxAN6l.js 124 kB 124 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/Media3DTop-CpdNXNf1.js 1.82 kB 1.82 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaAudioTop-qYtfOhNn.js 1.43 kB 1.43 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/MediaImageTop-vhWMCCeS.js 1.75 kB 1.75 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-2U4KtUml.js 360 kB 360 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-5rcOJVex.js 391 kB 391 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BTBSP0IQ.js 480 kB 480 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-BZEpP7w6.js 383 kB 383 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C-Tw-7I_.js 407 kB 407 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-C76rbmLh.js 441 kB 441 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-CxxD1ymF.js 395 kB 395 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-DciNsuYe.js 392 kB 392 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-G7wcMpUD.js 356 kB 356 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-nGdg-hIO.js 388 kB 388 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/nodeDefs-xiPSrG6S.js 440 kB 440 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/OBJLoader2WorkerModule-DTMpvldF.js 109 kB 109 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/previousFullPath-DeIFnh1k.js 665 B 665 B ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/rolldown-runtime-DLICfi3-.js 1.97 kB 1.97 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/WidgetImageCompare-DqrezrkI.js 3.1 kB 3.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B
assets/widgetPropFilter-Cd0O5LPR.js 1.1 kB 1.1 kB ⚪ 0 B ⚪ 0 B ⚪ 0 B

Status: 75 added / 75 removed

@DrJKL DrJKL requested a review from AustinMroz February 14, 2026 00:18
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/composables/graph/useGraphNodeManager.ts (1)

252-271: ⚠️ Potential issue | 🟠 Major

SafeWidgetData.name can desync slot metadata for promoted widgets.

Line 252-271 now sets name to sourceWidgetName, but slot metadata is keyed by input.widget.name (outer input name). refreshNodeSlots() later looks up by SafeWidgetData.name, so promoted widgets may stop receiving slotMetadata updates (link status, index). Consider keeping name as widget.name and, if needed, add a separate field for the interior name.

🛠️ Suggested fix (keep `name` aligned with slot metadata)
-      const name =
-        'sourceWidgetName' in widget
-          ? (widget as { sourceWidgetName: string }).sourceWidgetName
-          : widget.name
+      const name = widget.name
🤖 Fix all issues with AI agents
In `@src/components/graph/widgets/DomWidget.vue`:
- Around line 71-75: The selection logic uses a potentially stale reference:
when positionOverride exists you check overrideInGraph but still set ownerNode =
override.node; change ownerNode to use the graph-resolved instance
(overrideInGraph) when present so comparisons against selectedNode use the exact
node instance returned by lgCanvas.graph?.getNodeById; update the isSelected
calculation to compare selectedNode against that resolved ownerNode (keep using
widgetState.widget.node when no overrideInGraph).

In `@src/core/graph/subgraph/promotedWidgetView.test.ts`:
- Around line 51-52: The describe block is using a string title instead of a
function reference which violates vitest/prefer-describe-function-title; update
the test suite header to pass the function reference (createPromotedWidgetView)
instead of the string literal, e.g., replace
describe('createPromotedWidgetView', ...) with
describe(createPromotedWidgetView, ...) and apply the same pattern for other
suites in this file that reference functions or classes.
- Around line 19-29: The test file defines a module-level mutable mock object
(mockDomWidgetStore) which can leak state across tests; replace it with a
hoisted mock via vi.hoisted() and ensure you reset or reinitialize it in each
test's beforeEach. Specifically, change the module mock for useDomWidgetStore to
return a hoisted store (created with vi.hoisted()) and in your beforeEach
reassign or clear its properties (widgetStates map and the spy functions like
setPositionOverride/clearPositionOverride) so each test gets a fresh mock;
update references to mockDomWidgetStore, useDomWidgetStore, and the beforeEach
to perform the reset.

In `@src/core/graph/subgraph/promotedWidgetView.ts`:
- Around line 1-12: Replace the hardcoded "disconnected" / canvas placeholder
label strings in promotedWidgetView with vue-i18n calls: import/use the i18n
composable (useI18n or existing t(...) helper) in the component and replace
occurrences of the literal canvas label and the other user-facing strings around
the canvas (the block currently around lines ~224-238) with t('your.key.path')
calls; add corresponding keys to the English locale JSON (main.json) using
descriptive keys like "promotedWidget.disconnected" and
"promotedWidget.canvasLabel" and use those keys in the t(...) calls so all
user-facing text goes through i18n.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/components/graph/DomWidgets.vue`:
- Around line 54-55: The membership check uses
currentGraph.getNodeById(posNode.id) which can return a different node with the
same id from another graph; replace it with a graph-identity check so we only
treat nodes that actually belong to currentGraph as in-scope. Concretely, change
the isInCorrectGraph computation to compare the node's graph identity to
currentGraph (for example use posNode.graph === currentGraph or
posNode.getGraph() === currentGraph, or use a dedicated API like
currentGraph.containsNode(posNode) if available) instead of calling
currentGraph.getNodeById(posNode.id); keep nodeVisible =
lgCanvas.isNodeVisible(posNode) as-is.

In `@src/stores/domWidgetStore.ts`:
- Around line 13-16: PositionOverride's widget property is currently typed as
IBaseWidget which becomes a reactive proxy under Pinia and breaks
strict-equality checks; change the type to Raw<IBaseWidget> for
PositionOverride.widget so the stored widget stays the original object (not a
proxy). Update any other declarations/usages of positionOverride.widget in this
module (the same pattern around the other positionOverride handling) to use
Raw<IBaseWidget> as well so the strict-equality guard continues to work.
🧹 Nitpick comments (1)
src/stores/domWidgetStore.ts (1)

77-99: Prefer function declarations for new store actions.

Consider using function declarations for setPositionOverride and clearPositionOverride to align with the repository rule for pure functions.

Example refactor
-const setPositionOverride = (
-  widgetId: string,
-  override: PositionOverride
-) => {
+function setPositionOverride(widgetId: string, override: PositionOverride) {
   const state = widgetStates.value.get(widgetId)
   if (!state) return
   const current = state.positionOverride
   if (
     current &&
     current.node === override.node &&
     current.widget === override.widget
   )
     return
   state.positionOverride = {
     node: markRaw(override.node),
     widget: markRaw(override.widget)
   }
-}
+}

-const clearPositionOverride = (widgetId: string) => {
+function clearPositionOverride(widgetId: string) {
   const state = widgetStates.value.get(widgetId)
   if (state) state.positionOverride = undefined
-}
+}

As per coding guidelines "Do not use function expressions if it's possible to use function declarations instead".

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

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

770-782: ⚠️ Potential issue | 🟠 Major

Self-assignment in clone() appears ineffective.

Line 774 performs this.properties.proxyWidgets = this.properties.proxyWidgets on the original node, not the clone. Since properties.proxyWidgets is a plain property (no setter), this assignment has no effect. The comment suggests intent to "reset DOM widget ownership," but this won't happen.

If the intent is to give the clone its own copy of the promotion list to avoid shared state:

🐛 Proposed fix
   override clone() {
     const clone = super.clone()
-    // force reasign so domWidgets reset ownership
-
-    this.properties.proxyWidgets = this.properties.proxyWidgets
+    // Give the clone its own copy of the promotion list
+    clone.properties.proxyWidgets = [...(this.properties.proxyWidgets ?? [])]
 
     //TODO: Consider deep cloning subgraphs here.
🤖 Fix all issues with AI agents
In `@src/lib/litegraph/src/subgraph/SubgraphNode.ts`:
- Around line 673-693: The filter in SubgraphNode.ensureWidgetRemoved uses loose
equality (n == view.sourceNodeId); change it to strict equality so the predicate
becomes !(n === view.sourceNodeId && w === view.sourceWidgetName). Update the
comparison in the filter inside ensureWidgetRemoved (referencing
ensureWidgetRemoved, PromotedWidgetView, _getPromotionList, and
properties.proxyWidgets) to use === for both n vs view.sourceNodeId and w vs
view.sourceWidgetName to match the rest of the class.
🧹 Nitpick comments (1)
src/lib/litegraph/src/subgraph/SubgraphNode.ts (1)

185-191: Consider adding a debug warning in the empty setter.

The empty setter silently discards assignments to this.widgets. While intentional (SubgraphNodes have no native widgets), this could mask bugs in calling code that expects assignment to work. A development-time warning could aid debugging.

💡 Optional: Add debug warning
     Object.defineProperty(this, 'widgets', {
       get: () => this._getPromotedViews(),
-      set: () => {},
+      set: () => {
+        if (import.meta.env.DEV) {
+          console.warn('[SubgraphNode] Direct widget assignment is ignored; use promotion list instead.')
+        }
+      },
       configurable: true,
       enumerable: true
     })

@DrJKL DrJKL added the New Browser Test Expectations New browser test screenshot should be set by github action label Feb 14, 2026
@github-actions
Copy link

Updating Playwright Expectations

@github-actions github-actions bot removed the New Browser Test Expectations New browser test screenshot should be set by github action label Feb 14, 2026
DrJKL and others added 17 commits February 14, 2026 02:38
…es 1-2)

Replace SubgraphNode.widgets with a synthetic getter backed by
lightweight PromotedWidgetView objects resolved from properties.proxyWidgets.

Phase 1: Create promotedWidgetView.ts factory
- Factory function creates plain objects with property descriptors
- Delegates type/options/tooltip to interior widget via getters
- Store-backed value/label/hidden via WidgetValueStore
- Drawing delegates to toConcreteWidget with disconnected placeholder fallback

Phase 2: Synthetic widgets getter on SubgraphNode
- Replace `override widgets = []` with declare + Object.defineProperty getter
- Memoized _getPromotionList() invalidates on reference change
- _getPromotedViews() with view cache for positional stability
- _resolveLegacyEntry() handles -1 migration from old format
- Remove onConfigure patching and dead proxy code from proxyWidget.ts
- Update _internalConfigureAfterSlots to clear view cache

Amp-Thread-ID: https://ampcode.com/threads/T-019c58a0-5dcd-7670-8b26-b166cef85eee
Co-authored-by: Amp <amp@ampcode.com>
…list + view cache

Phase 3 of proxy-widget-v2: _setWidget no longer creates widget copies

via Object.assign with getter overrides. Instead it adds [nodeId, widgetName]

to properties.proxyWidgets and creates/retrieves a PromotedWidgetView from

_viewCache. Added interiorNode param to _setWidget and node field to

SubgraphInputEventMap input-connected event. Removed BaseWidget/AssetWidget

imports from SubgraphNode.ts.

Amp-Thread-ID: https://ampcode.com/threads/T-019c58c1-f590-731f-bace-df9771a83125
Co-authored-by: Amp <amp@ampcode.com>
- Remove isProxyWidget, isDisconnectedWidget, DisconnectedWidget.ts
- Simplify subgraph-opened handler to use synthetic widgets getter
- Override ensureWidgetRemoved to remove from promotion list + view cache
- Override removeWidgetByName to delegate to ensureWidgetRemoved
- Simplify onRemoved: clear _viewCache, dispatch widget-demoted for all
- Update all consumers to use sourceNodeId/sourceWidgetName pattern
- Update pruneDisconnected to validate against actual subgraph nodes
- Net: -55 lines, 11 files changed

Amp-Thread-ID: https://ampcode.com/threads/T-019c58cd-90c7-75c0-917f-74b0b626e4de
Co-authored-by: Amp <amp@ampcode.com>
- Override removeWidget on SubgraphNode to delegate to ensureWidgetRemoved

- Verified serialize() skips promoted views (serialize: false)

- Verified serialize() value sync for System 1 views via slot name matching

- Added tests: removeWidget, removeWidgetByName, input cleanup, serialize behavior

Amp-Thread-ID: https://ampcode.com/threads/T-019c58df-099f-753a-82d7-ca25c1aebc76
Co-authored-by: Amp <amp@ampcode.com>
- 31 new tests for createPromotedWidgetView, SubgraphNode.widgets
  getter caching/memoization, promote/demote cycle, disconnected state
- Verified old infrastructure already deleted (Phases 2-4)
- Confirmed zero references to PromotedWidgetSlot, PromotedDomWidgetAdapter
- useGraphNodeManager.ts needs no changes (uses sourceNodeId check)

Amp-Thread-ID: https://ampcode.com/threads/T-019c58e4-c39a-704b-895e-d6d4b1ac2ebc
Co-authored-by: Amp <amp@ampcode.com>
extractVueNodeData was overwriting SubgraphNode's synthetic widgets
getter with a static shallowReactive snapshot, preventing Vue from
seeing promote/demote changes. Now detects existing getter via
Object.getOwnPropertyDescriptor, preserves it, and syncs results
into the reactive array on each access.

All Phase 7 validation criteria verified:
- DOM widget promotion renders placeholder
- Canvas/Vue rendering of promoted widgets
- RSP promote/demote/reorder
- Context menu promotion
- Disconnect/reconnect interior widget
- Nested subgraphs
- 0 fix commits across all phases

Amp-Thread-ID: https://ampcode.com/threads/T-019c58ee-31eb-76aa-beec-ccddc42484bb
Co-authored-by: Amp <amp@ampcode.com>
Promoted DOM widgets (textarea, image upload, component widgets) now
render their actual element on the SubgraphNode surface instead of a
text placeholder.

- Add positionOverride to DomWidgetState for redirecting widget
  positioning to SubgraphNode
- Register override in PromotedWidgetView via y setter and draw()
- Fall back to interior node when inside the subgraph (override node
  not in current graph)
- Fix clipping: DomWidget.vue uses getNodeById fallback for ownerNode
- Fix active flag: bypass active check when override is valid
- Add PromotedWidgetView.node to interface, extract isPromotedWidgetView
  type guard
- Clean up overrides on demote/removal in SubgraphNode
- Add 6 new tests for override behavior

Amp-Thread-ID: https://ampcode.com/threads/T-019c595f-ee25-775a-a71f-cbac34aa6167
Co-authored-by: Amp <amp@ampcode.com>
Use graph identity check instead of getNodeById for override visibility (IDs not unique across graphs).

Add equality guard in setPositionOverride to skip redundant reactive updates on every frame.

Amp-Thread-ID: https://ampcode.com/threads/T-019c597d-ab3f-74cc-86e2-7d03c14b63df
Co-authored-by: Amp <amp@ampcode.com>
- When a promoted widget is connected to a SubgraphInput node, unpromote it first so it transitions to being linked via the input

- Fix serialize() to use input._widget directly instead of ambiguous name-based matching that could sync wrong values across widgets with the same name

Amp-Thread-ID: https://ampcode.com/threads/T-019c5984-fadf-759e-909e-d298c740484a
Co-authored-by: Amp <amp@ampcode.com>
…ous name lookup

When multiple widgets share the same name (e.g. two CLIP 'text' widgets),

removeWidgetByName picked the first match, removing the wrong widget.

Use the specific PromotedWidgetView bound to the input slot instead.

Amp-Thread-ID: https://ampcode.com/threads/T-019c59c8-2b8c-7297-bc0f-5c7070323219
Co-authored-by: Amp <amp@ampcode.com>
…body

Promoted DOM widgets overlay the node body and intercept pointer events.

Amp-Thread-ID: https://ampcode.com/threads/T-019c55f5-bf2b-7087-b0b8-0ac3be03c7c9
Co-authored-by: Amp <amp@ampcode.com>
…test

- Cache _getPromotedViews result for reference stability (widgets getter)
- Replace Object.create(null) with {} for standard Object prototype
- Revert navigateIntoSubgraph to dblclick approach (title button click fails in CI)

Amp-Thread-ID: https://ampcode.com/threads/T-019c59d8-632b-750c-a1a2-93920c89f63e
Co-authored-by: Amp <amp@ampcode.com>
…omotion

Replace 4 memoization fields in SubgraphNode and multiple independent
parseProxyWidgets() calls with a single Pinia store (reactive Map).

- Add usePromotionStore with getPromotions, isPromoted, setPromotions,
  promote, demote actions
- Hydrate store from properties.proxyWidgets on SubgraphNode.onConfigure
- Sync store back to properties.proxyWidgets on SubgraphNode.serialize
- Remove _proxyWidgetsRaw, _promotionList, _viewsCached, _viewsCacheDirty
  fields and _getPromotionList() from SubgraphNode
- Remove customRef patterns from SubgraphEditor.vue and TabSubgraphInputs.vue
- Remove matchesPropertyItem, matchesWidgetItem, getProxyWidgets helpers
- Remove emit('update:proxyWidgets') + parent handler in RightSidePanel.vue
- Remove unused exports: widgetItemToProperty, PromotionEntry,
  ProxyWidgetsProperty

Amp-Thread-ID: https://ampcode.com/threads/T-019c5afe-a7fd-725d-9efc-557b672e55a8
Co-authored-by: Amp <amp@ampcode.com>
- Export getWidgetName from proxyWidgetUtils, remove duplicate in SubgraphEditor

- Add movePromotion action to promotionStore, use in TabSubgraphInputs

- Use parents.some() instead of parents[0] in SectionWidgets

Amp-Thread-ID: https://ampcode.com/threads/T-019c5afe-a7fd-725d-9efc-557b672e55a8
Co-authored-by: Amp <amp@ampcode.com>
SubgraphNode now calls usePromotionStore() during configuration, requiring an active Pinia instance in tests.

Amp-Thread-ID: https://ampcode.com/threads/T-019c5b49-c288-7247-8976-cb5a6451be02
Co-authored-by: Amp <amp@ampcode.com>
DrJKL and others added 6 commits February 22, 2026 14:23
getNodes() serializes the entire LGraphNode object graph across CDP, which fails in CI due to reactive proxies and circular references. All callers only needed .length, so add a getNodeCount() helper that returns a primitive.

Amp-Thread-ID: https://ampcode.com/threads/T-019c8779-b3d2-719f-a3cf-cc7b1d8fe0aa
Co-authored-by: Amp <amp@ampcode.com>
- proxyWidgetUtils.ts -> promotionUtils.ts

- proxyWidget.ts (schema) -> promotionSchema.ts

- PromotedWidgetViewImpl -> PromotedWidgetView (drop Impl suffix)

Amp-Thread-ID: https://ampcode.com/threads/T-019c87b7-2cba-736a-9d48-adb1505464b8
Co-authored-by: Amp <amp@ampcode.com>
@socket-security
Copy link

socket-security bot commented Feb 23, 2026

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedextendable-media-recorder-wav-encoder@​7.0.12999100669080
Addedextendable-media-recorder@​9.2.27991009190100

View full report

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

🧹 Nitpick comments (6)
browser_tests/tests/dialog.spec.ts (1)

57-64: Consider simplifying the nested assertion pattern.

The nested expect(async () => { await expect(...).not.toBeVisible() }).toPass() adds an extra retry layer over Playwright's built-in auto-waiting. While this defensive approach works for flaky undo/redo timing, it's verbose.

A simpler alternative would be to increase the assertion timeout directly:

await expect(missingNodesWarning).not.toBeVisible({ timeout: 5000 })

That said, if the double-retry is intentional to handle edge cases where undo/redo state propagation is unpredictable, the current approach is acceptable.

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

In `@browser_tests/tests/dialog.spec.ts` around lines 57 - 64, The nested retry
pattern wraps an assertion inside expect(...).toPass() which duplicates
Playwright's auto-waiting and is verbose; replace the two occurrences that wrap
await expect(missingNodesWarning).not.toBeVisible() (the blocks after the
initial check and after comfyPage.keyboard.redo()) with a single direct
assertion that increases the timeout on the Playwright matcher (use
expect(missingNodesWarning).not.toBeVisible with a timeout of 5000) so the code
relies on Playwright's built-in waiting instead of the extra toPass wrapper.
browser_tests/fixtures/helpers/NodeOperationsHelper.ts (1)

20-24: Consider unifying or differentiating the two node-count methods.

getNodeCount() and getGraphNodesCount() both return the node count but differ in null-safety: getGraphNodesCount() uses optional chaining with a 0 fallback, while getNodeCount() uses non-null assertions that will throw if app or graph is undefined.

If both are needed, consider documenting when to use each. If only one is needed long-term, consider deprecating or removing the other to avoid confusion.

Also applies to: 36-38

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

In `@browser_tests/fixtures/helpers/NodeOperationsHelper.ts` around lines 20 - 24,
There are two methods, getNodeCount() and getGraphNodesCount(), that return the
same value but differ in null-safety; pick one approach and make them
consistent: either (A) unify to the safe implementation used by
getGraphNodesCount() (use optional chaining and a 0 fallback) and remove or
deprecate the other, or (B) keep both but add clear JSDoc above getNodeCount()
and getGraphNodesCount() in NodeOperationsHelper.ts explaining their distinct
contracts (e.g., getNodeCount() throws on missing app/graph,
getGraphNodesCount() returns 0 if missing) so callers know which to use; prefer
option A (convert getNodeCount() to use optional chaining and fallback to 0, or
remove it) to avoid surprising exceptions.
lint-staged.config.ts (1)

9-21: Prefer a named function and immutable command list construction.

This keeps the rule compliant with the “no function expressions” and “avoid mutable state” guidelines while preserving behavior.

🔧 Suggested refactor
 export default {
   'tests-ui/**': () =>
     'echo "Files in tests-ui/ are deprecated. Colocate tests with source files." && exit 1',

   './**/*.js': (stagedFiles: string[]) => formatAndEslint(stagedFiles),

-  './**/*.{ts,tsx,vue,mts}': (stagedFiles: string[]) => {
-    const commands = [...formatAndEslint(stagedFiles), 'pnpm typecheck']
-
-    const hasBrowserTestsChanges = stagedFiles
-      .map((f) => path.relative(process.cwd(), f).replace(/\\/g, '/'))
-      .some((f) => f.startsWith('browser_tests/'))
-
-    if (hasBrowserTestsChanges) {
-      commands.push('pnpm typecheck:browser')
-    }
-
-    return commands
-  }
+  './**/*.{ts,tsx,vue,mts}': lintStagedTypes
 }
+
+function lintStagedTypes(stagedFiles: string[]) {
+  const hasBrowserTestsChanges = stagedFiles
+    .map((f) => path.relative(process.cwd(), f).replace(/\\/g, '/'))
+    .some((f) => f.startsWith('browser_tests/'))
+
+  return [
+    ...formatAndEslint(stagedFiles),
+    'pnpm typecheck',
+    ...(hasBrowserTestsChanges ? ['pnpm typecheck:browser'] : [])
+  ]
+}

As per coding guidelines, “Avoid mutable state; prefer immutability and assignment at point of declaration” and “Do not use function expressions if it's possible to use function declarations instead.”

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

In `@lint-staged.config.ts` around lines 9 - 21, Replace the inline arrow function
with a named function (e.g., function buildSourceCommands(stagedFiles:
string[])) and remove the mutable push by building the commands immutably:
create a const baseCommands = [...formatAndEslint(stagedFiles), 'pnpm
typecheck'] and then set const commands = hasBrowserTestsChanges ?
[...baseCommands, 'pnpm typecheck:browser'] : baseCommands; keep the same
hasBrowserTestsChanges calculation, return commands, and use the new function
name where the rule value currently references the arrow function.
src/composables/node/useNodeCanvasImagePreview.test.ts (1)

43-46: Consider importing the constant instead of hardcoding the string.

The test hardcodes '$$canvas-image-preview' rather than using the exported CANVAS_IMAGE_PREVIEW_WIDGET constant. This creates a maintenance risk if the constant value changes.

♻️ Suggested fix
-import { useNodeCanvasImagePreview } from './useNodeCanvasImagePreview'
+import {
+  CANVAS_IMAGE_PREVIEW_WIDGET,
+  useNodeCanvasImagePreview
+} from './useNodeCanvasImagePreview'

Then update the assertion:

     expect(imagePreviewWidget).toHaveBeenCalledWith(node, {
       type: 'IMAGE_PREVIEW',
-      name: '$$canvas-image-preview'
+      name: CANVAS_IMAGE_PREVIEW_WIDGET
     })

Also update line 52:

-    node.addWidget('text', '$$canvas-image-preview', '', () => undefined, {})
+    node.addWidget('text', CANVAS_IMAGE_PREVIEW_WIDGET, '', () => undefined, {})
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/composables/node/useNodeCanvasImagePreview.test.ts` around lines 43 - 46,
Replace the hardcoded widget name in the test assertion with the exported
constant CANVAS_IMAGE_PREVIEW_WIDGET: in useNodeCanvasImagePreview.test.ts
update the expect call that invokes imagePreviewWidget(node, { type:
'IMAGE_PREVIEW', name: '$$canvas-image-preview' }) to use name:
CANVAS_IMAGE_PREVIEW_WIDGET (importing it at the top of the test), and similarly
replace any other occurrences of the literal '$$canvas-image-preview' in this
test (e.g., the later assertion at line 52) with the same imported constant to
keep the test in sync with the source constant.
browser_tests/fixtures/utils/litegraphUtils.ts (1)

480-485: Prefer a function declaration for the local helper.

You can keep the same behavior while following the project rule by capturing comfyPage and using a declaration.

♻️ Suggested refactor
-    const checkIsInSubgraph = async () => {
-      return this.comfyPage.page.evaluate(() => {
+    const { comfyPage } = this
+    async function checkIsInSubgraph() {
+      return comfyPage.page.evaluate(() => {
         const graph = window.app!.canvas.graph
         return !!graph && 'inputNode' in graph
       })
     }
As per coding guidelines, "Do not use function expressions if it's possible to use function declarations instead".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@browser_tests/fixtures/utils/litegraphUtils.ts` around lines 480 - 485, The
local helper is defined as an arrow function expression; convert it to an async
function declaration named checkIsInSubgraph that closes over comfyPage (i.e.,
keep using comfyPage.page.evaluate) so behavior is identical: replace "const
checkIsInSubgraph = async () => { return this.comfyPage.page.evaluate(... ) }"
with "async function checkIsInSubgraph() { return comfyPage.page.evaluate(() =>
{ const graph = window.app!.canvas.graph; return !!graph && 'inputNode' in graph
}) }" (ensure references to comfyPage, comfyPage.page.evaluate,
window.app!.canvas.graph and the 'inputNode' check remain unchanged).
src/composables/graph/useGraphNodeManager.test.ts (1)

85-115: Avoid hard-coded interior node id in promotion test

Using the actual node id keeps the test resilient if ID assignment ever changes.

♻️ Suggested tweak
     const interiorNode = new LGraphNode('interior')
     interiorNode.id = 10
     subgraph.add(interiorNode)
+    const interiorNodeId = String(interiorNode.id)
 
     usePromotionStore().promote(
       subgraphNode.rootGraph.id,
       subgraphNode.id,
-      '10',
+      interiorNodeId,
       '$$canvas-image-preview'
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/composables/graph/useGraphNodeManager.test.ts` around lines 85 - 115, The
test hard-codes the interior node id ('10') when calling
usePromotionStore().promote; replace that literal with the actual
interiorNode.id (converted to string if promote expects a string) to keep the
test resilient—i.e., use String(interiorNode.id) (or interiorNode.id) in the
call to usePromotionStore().promote so the test uses the real id from the
LGraphNode instance created above.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@browser_tests/tests/colorPalette.spec.ts`:
- Around line 248-259: The test silently passes if app.graph.serialize is
missing because the inline getter returns { nodes: [] }; instead, detect the
absence of serialize and fail the test or return an explicit flag so the
assertion can't be bypassed: check for the presence of (globalThis as { app?: {
graph?: { serialize: () => unknown } } }).app?.graph?.serialize and if missing
either throw an Error or return { missingSerialize: true } and then add an
assertion on parsed.missingSerialize === undefined (or assert serialize exists)
before using parsed.nodes; update references to app.graph.serialize and
parsed.nodes accordingly so the test fails loudly when serialization is
unavailable.

In `@src/components/rightSidePanel/parameters/SectionWidgets.vue`:
- Around line 78-92: The call to promotionStore.isPromoted in SectionWidgets.vue
can miss matches because widget.sourceNodeId may be a number while other call
sites pass string IDs; update the promoted-lookup branch in the anonymous
function returned by parents.some to normalize the source ID by calling
String(widget.sourceNodeId) (just like the fallback uses String(widgetNode.id)),
so both branches call promotionStore.isPromoted with string IDs and the same
parameters used elsewhere (isPromotedWidgetView, promotionStore.isPromoted,
sourceWidgetName).

In `@src/components/rightSidePanel/subgraph/SubgraphEditor.vue`:
- Around line 170-195: The bulk promotion functions showAll, hideAll, and
showRecommended update promotionStore but don’t trigger a canvas redraw; after
their loops you should mark the canvas as dirty or emit the app’s invalidate
event (for example call your canvas invalidation helper such as
canvasStore.markDirty() or emit('invalidate-canvas')) so the UI repaints; update
showAll, hideAll and showRecommended to call that invalidation method after
performing all promotionStore.promote()/demote() calls.

---

Nitpick comments:
In `@browser_tests/fixtures/helpers/NodeOperationsHelper.ts`:
- Around line 20-24: There are two methods, getNodeCount() and
getGraphNodesCount(), that return the same value but differ in null-safety; pick
one approach and make them consistent: either (A) unify to the safe
implementation used by getGraphNodesCount() (use optional chaining and a 0
fallback) and remove or deprecate the other, or (B) keep both but add clear
JSDoc above getNodeCount() and getGraphNodesCount() in NodeOperationsHelper.ts
explaining their distinct contracts (e.g., getNodeCount() throws on missing
app/graph, getGraphNodesCount() returns 0 if missing) so callers know which to
use; prefer option A (convert getNodeCount() to use optional chaining and
fallback to 0, or remove it) to avoid surprising exceptions.

In `@browser_tests/fixtures/utils/litegraphUtils.ts`:
- Around line 480-485: The local helper is defined as an arrow function
expression; convert it to an async function declaration named checkIsInSubgraph
that closes over comfyPage (i.e., keep using comfyPage.page.evaluate) so
behavior is identical: replace "const checkIsInSubgraph = async () => { return
this.comfyPage.page.evaluate(... ) }" with "async function checkIsInSubgraph() {
return comfyPage.page.evaluate(() => { const graph = window.app!.canvas.graph;
return !!graph && 'inputNode' in graph }) }" (ensure references to comfyPage,
comfyPage.page.evaluate, window.app!.canvas.graph and the 'inputNode' check
remain unchanged).

In `@browser_tests/tests/dialog.spec.ts`:
- Around line 57-64: The nested retry pattern wraps an assertion inside
expect(...).toPass() which duplicates Playwright's auto-waiting and is verbose;
replace the two occurrences that wrap await
expect(missingNodesWarning).not.toBeVisible() (the blocks after the initial
check and after comfyPage.keyboard.redo()) with a single direct assertion that
increases the timeout on the Playwright matcher (use
expect(missingNodesWarning).not.toBeVisible with a timeout of 5000) so the code
relies on Playwright's built-in waiting instead of the extra toPass wrapper.

In `@lint-staged.config.ts`:
- Around line 9-21: Replace the inline arrow function with a named function
(e.g., function buildSourceCommands(stagedFiles: string[])) and remove the
mutable push by building the commands immutably: create a const baseCommands =
[...formatAndEslint(stagedFiles), 'pnpm typecheck'] and then set const commands
= hasBrowserTestsChanges ? [...baseCommands, 'pnpm typecheck:browser'] :
baseCommands; keep the same hasBrowserTestsChanges calculation, return commands,
and use the new function name where the rule value currently references the
arrow function.

In `@src/composables/graph/useGraphNodeManager.test.ts`:
- Around line 85-115: The test hard-codes the interior node id ('10') when
calling usePromotionStore().promote; replace that literal with the actual
interiorNode.id (converted to string if promote expects a string) to keep the
test resilient—i.e., use String(interiorNode.id) (or interiorNode.id) in the
call to usePromotionStore().promote so the test uses the real id from the
LGraphNode instance created above.

In `@src/composables/node/useNodeCanvasImagePreview.test.ts`:
- Around line 43-46: Replace the hardcoded widget name in the test assertion
with the exported constant CANVAS_IMAGE_PREVIEW_WIDGET: in
useNodeCanvasImagePreview.test.ts update the expect call that invokes
imagePreviewWidget(node, { type: 'IMAGE_PREVIEW', name: '$$canvas-image-preview'
}) to use name: CANVAS_IMAGE_PREVIEW_WIDGET (importing it at the top of the
test), and similarly replace any other occurrences of the literal
'$$canvas-image-preview' in this test (e.g., the later assertion at line 52)
with the same imported constant to keep the test in sync with the source
constant.

Copy link
Contributor

@christian-byrne christian-byrne left a comment

Choose a reason for hiding this comment

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

Nice refactoring in the latest commits. The PromotedWidgetViewManager cache reconciliation pattern is clean, and centralizing resolvePromotedWidgetSource reduces duplication.

One minor observation on getProjectedWidget in promotedWidgetView.ts (lines 257-280): the cache invalidation check compares projectedWidget.type !== resolved.widget.type after already checking node/widget identity. If the source widget type can change while the widget reference stays the same, this is correct. Otherwise this comparison is redundant since a changed type would imply a different widget reference. Not blocking, just a clarity question.

The getNodeCount() fix for CDP serialization is a good catch - avoids serializing the entire reactive node graph across the protocol boundary.

- Remove redundant widget type comparison in promotedWidgetView cache
  invalidation (type is immutable on widget instances)
- Make colorPalette E2E test fail loudly when app.graph.serialize is
  unavailable instead of silently passing with empty nodes

Amp-Thread-ID: https://ampcode.com/threads/T-019c880b-42ef-7156-adad-0bccd20e5d79
Co-authored-by: Amp <amp@ampcode.com>
@DrJKL DrJKL added the preview label Feb 23, 2026
@christian-byrne
Copy link
Contributor

Nice refactoring in the latest commits. The PromotedWidgetViewManager cache reconciliation pattern is clean, and centralizing resolvePromotedWidgetSource reduces duplication.

One minor observation on getProjectedWidget in promotedWidgetView.ts (lines 257-280): the cache invalidation check compares projectedWidget.type !== resolved.widget.type after already checking node/widget identity. If the source widget type can change while the widget reference stays the same, this is correct. Otherwise this comparison is redundant since a changed type would imply a different widget reference. Not blocking, just a clarity question.

The getNodeCount() fix for CDP serialization is a good catch - avoids serializing the entire reactive node graph across the protocol boundary.

DrJKL and others added 3 commits February 23, 2026 00:18
PromotedWidgetView defines options/type/name/value as prototype getters, which object spread silently drops. Build SimplifiedWidget by reading properties directly and querying widgetValueStore, matching the NodeWidgets.vue canvas pattern.

- Replace enhancedWidget spread with explicit simplifiedWidget computed

- Resolve source widget via resolvePromotedWidgetSource

- Query widgetValueStore for value/label/options

- Export getControlWidget from useGraphNodeManager

- Add WidgetItem tests for promoted widget property passthrough

Co-authored-by: Amp <amp@ampcode.com>
Amp-Thread-ID: https://ampcode.com/threads/T-019c88f3-8080-70c9-a9fc-df268c580fd9
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: 3

♻️ Duplicate comments (3)
src/components/rightSidePanel/subgraph/SubgraphEditor.vue (1)

170-195: ⚠️ Potential issue | 🟡 Minor

Invalidate the canvas after bulk promotion updates.

Bulk promote/demote operations update the store but don’t force a redraw, so the UI can stay stale until the next repaint.

🔧 Proposed fix
 function showAll() {
   const node = activeNode.value
   if (!node) return
   for (const [n, w] of filteredCandidates.value) {
     promotionStore.promote(node.rootGraph.id, node.id, String(n.id), w.name)
   }
+  canvasStore.canvas?.setDirty(true, true)
 }
 function hideAll() {
   const node = activeNode.value
   if (!node) return
   for (const [n, w] of filteredActive.value) {
     if (String(n.id) === '-1') continue
     promotionStore.demote(
       node.rootGraph.id,
       node.id,
       String(n.id),
       getWidgetName(w)
     )
   }
+  canvasStore.canvas?.setDirty(true, true)
 }
 function showRecommended() {
   const node = activeNode.value
   if (!node) return
   for (const [n, w] of recommendedWidgets.value) {
     promotionStore.promote(node.rootGraph.id, node.id, String(n.id), w.name)
   }
+  canvasStore.canvas?.setDirty(true, true)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/rightSidePanel/subgraph/SubgraphEditor.vue` around lines 170 -
195, The bulk promote/demote functions (showAll, hideAll, showRecommended)
update promotionStore but don't trigger a canvas redraw, leaving the UI stale;
after calling promotionStore.promote or promotionStore.demote in those
functions, call the canvas invalidation/redraw helper (e.g., invoke the existing
invalidateCanvas/refreshCanvas/forceRerender function used elsewhere in this
component) once after the loop so the graph re-renders; ensure you reference
showAll, hideAll, showRecommended and promotionStore.promote/demote and batch
the redraw call outside the loops to avoid excessive repaints.
src/components/rightSidePanel/parameters/SectionWidgets.vue (1)

78-85: ⚠️ Potential issue | 🟡 Minor

Normalize promoted source node IDs before promotion lookup.

widget.sourceNodeId can be numeric, which can cause promotionStore lookups to miss. Normalize to string for consistency with other call sites.

🔧 Proposed fix
-        widget.sourceNodeId,
+        String(widget.sourceNodeId),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/rightSidePanel/parameters/SectionWidgets.vue` around lines 78
- 85, The promotion lookup can miss because widget.sourceNodeId may be a number;
update the call site inside the parents.some block (where
isPromotedWidgetView(widget) leads to promotionStore.isPromoted(...)) to
normalize the source node id to a string (e.g., pass String(widget.sourceNodeId)
or widget.sourceNodeId?.toString()) when calling promotionStore.isPromoted so
the lookup uses the same string-formatted ID as other call sites.
browser_tests/tests/subgraphPromotion.spec.ts (1)

158-160: ⚠️ Potential issue | 🟡 Minor

Use the centralized TestIds for the subgraph enter button.

Hard-coding the test id string bypasses the selector registry. Use the shared TestIds constant instead.

🔧 Proposed fix
-        const enterButton = subgraphVueNode.getByTestId('subgraph-enter-button')
+        const enterButton = subgraphVueNode.getByTestId(
+          TestIds.widgets.subgraphEnterButton
+        )

As per coding guidelines, "Use the centralized TestIds from browser_tests/fixtures/selectors.ts for test element selection".

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

In `@browser_tests/tests/subgraphPromotion.spec.ts` around lines 158 - 160,
Replace the hard-coded test id string in the subgraph test with the centralized
TestIds constant: update the selector in the subgraphPromotion.spec.ts where you
call subgraphVueNode.getByTestId('subgraph-enter-button') to use the shared
TestIds identifier (e.g., TestIds.SUBGRAPH_ENTER_BUTTON) from
browser_tests/fixtures/selectors.ts and import TestIds at the top if not already
present; keep the variable name enterButton and the getByTestId call otherwise
unchanged.
🧹 Nitpick comments (7)
src/components/rightSidePanel/parameters/WidgetItem.test.ts (2)

156-203: Tests validate propagation but could benefit from additional edge cases.

The current tests verify that widget properties are correctly passed through to the stub component. This is valuable coverage for the promoted widget refactor.

Consider adding tests for:

  • Edge case when options is undefined or empty
  • Behavior when resolvePromotedWidgetSource returns a defined value (currently mocked to return undefined)

Would you like me to draft additional test cases for these edge scenarios?

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

In `@src/components/rightSidePanel/parameters/WidgetItem.test.ts` around lines 156
- 203, Add tests covering edge cases for promoted widget propagation: add one
test using createMockPromotedWidgetView with options undefined or {} and assert
stub.props('widget').options is undefined or an empty object as expected (via
mountWidgetItem and StubWidgetComponent), and add another test that stubs
resolvePromotedWidgetSource to return a concrete promoted source value then
mountWidgetItem(createMockPromotedWidgetView(...)) and assert the stub receives
the resolved properties (options/type/name/value) to verify behavior when
resolvePromotedWidgetSource returns a defined value.

74-95: Consider using satisfies for mock object type validation.

Per repository learnings, when creating test helper functions that construct mock objects implementing an interface, prefer using satisfies InterfaceType for shape validation instead of as unknown as type assertions.

♻️ Suggested improvement
 function createMockNode(overrides: Partial<LGraphNode> = {}): LGraphNode {
-  return {
+  const base = {
     id: 1,
     type: 'TestNode',
     isSubgraphNode: () => false,
     graph: { rootGraph: { id: 'test-graph-id' } },
     ...overrides
-  } as unknown as LGraphNode
+  }
+  return base as unknown as LGraphNode
 }

 function createMockWidget(overrides: Partial<IBaseWidget> = {}): IBaseWidget {
-  return {
+  const base = {
     name: 'test_widget',
     type: 'combo',
     value: 'option_a',
     y: 0,
     options: {
       values: ['option_a', 'option_b', 'option_c']
     },
     ...overrides
-  } as IBaseWidget
+  }
+  return base as IBaseWidget
 }

Note: Full satisfies usage would require defining the complete interface shape, which may be impractical for partial mocks. The current approach is acceptable given the complexity of LGraphNode and IBaseWidget interfaces.

Based on learnings: "prefer using satisfies InterfaceType for shape validation instead of type assertions like as Partial<InterfaceType> as InterfaceType or as any."

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

In `@src/components/rightSidePanel/parameters/WidgetItem.test.ts` around lines 74
- 95, Replace the unsafe "as unknown as" assertions in the test helpers with
TypeScript's "satisfies" to validate the mock shapes: update createMockNode to
return an object expression that uses "satisfies LGraphNode" (keeping the
overrides: Partial<LGraphNode> and spread) and update createMockWidget similarly
to use "satisfies IBaseWidget" for the widget shape; this preserves flexible
Partial overrides while enabling compile-time shape checks for createMockNode
and createMockWidget.
browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts (1)

21-26: Consider using .first().toBeVisible() for more idiomatic Playwright assertions.

The expect.poll() with .count() works, but Playwright's built-in auto-retrying assertions are generally preferred. Using .first().toBeVisible() is more idiomatic when asserting "at least one element exists."

♻️ Suggested refactor
-    await expect
-      .poll(() => comfyPage.page.getByText('Error loading image').count())
-      .toBeGreaterThan(0)
-    await expect
-      .poll(() => comfyPage.page.getByText('Error loading video').count())
-      .toBeGreaterThan(0)
+    await expect(
+      comfyPage.page.getByText('Error loading image').first()
+    ).toBeVisible()
+    await expect(
+      comfyPage.page.getByText('Error loading video').first()
+    ).toBeVisible()

As per coding guidelines: Follow Playwright Best Practices — built-in locator assertions auto-retry and provide better error messages on failure.

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

In `@browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts` around lines
21 - 26, Replace the manual expect.poll(... .count()) checks with Playwright's
built-in locator assertions: use the getByText(...) locator's .first() and call
toBeVisible() so the assertions auto-retry and give better errors; specifically
update the two assertions that use comfyPage.page.getByText('Error loading
image') and comfyPage.page.getByText('Error loading video') to assert
.first().toBeVisible() instead of polling counts.
browser_tests/tests/menu.spec.ts (2)

206-210: Prefer non-retrying assertions inside toPass() blocks.

Using auto-retrying Playwright assertions like expect(menu).toBeVisible() inside toPass() can cause inefficient retry behavior—each inner assertion waits its full timeout before failing, then the outer toPass retries the entire block.

Consider using synchronous checks with .isVisible() for the locator assertions:

♻️ Suggested refactor
      await expect(async () => {
-       await expect(menu).toBeVisible()
-       await expect(themeSubmenu).toBeVisible()
+       expect(await menu.isVisible()).toBe(true)
+       expect(await themeSubmenu.isVisible()).toBe(true)
        expect(await topbar.isMenuItemActive(lightThemeItem)).toBe(true)
      }).toPass({ timeout: 5000 })

Based on learnings: Playwright best practices recommend that assertions inside toPass callbacks should not be auto-retrying.

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

In `@browser_tests/tests/menu.spec.ts` around lines 206 - 210, The toPass block
currently contains auto-retrying Playwright assertions
(expect(menu).toBeVisible(), expect(themeSubmenu).toBeVisible()) which causes
double retrying; change those to synchronous visibility checks by calling await
menu.isVisible() and await themeSubmenu.isVisible() and assert the booleans
(e.g. expect(await menu.isVisible()).toBe(true)) inside the async callback
passed to toPass while keeping the existing call to await
topbar.isMenuItemActive(lightThemeItem) (or assert its boolean similarly); in
short, replace auto-retrying toBeVisible() calls with .isVisible() checks and
simple boolean expectations inside the toPass block so only the outer retry is
exercised.

231-238: Same refactor applies here—prefer non-retrying assertions inside toPass().

♻️ Suggested refactor
      await expect(async () => {
-       await expect(menu).toBeVisible()
-       await expect(themeItems2.submenu).toBeVisible()
+       expect(await menu.isVisible()).toBe(true)
+       expect(await themeItems2.submenu.isVisible()).toBe(true)
        expect(await topbar.isMenuItemActive(themeItems2.darkTheme)).toBe(true)
        expect(await topbar.isMenuItemActive(themeItems2.lightTheme)).toBe(
          false
        )
      }).toPass({ timeout: 5000 })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@browser_tests/tests/menu.spec.ts` around lines 231 - 238, Inside the toPass
wrapper you currently call Playwright's retrying assertions (await
expect(menu).toBeVisible() and await expect(themeItems2.submenu).toBeVisible());
replace those with non-retrying checks or move them outside toPass: either await
the visibility first (await expect(menu).toBeVisible(); await
expect(themeItems2.submenu).toBeVisible();) before entering the toPass block, or
convert the calls inside toPass to synchronous checks using isVisible() (e.g.,
expect(await menu.isVisible()).toBe(true) and expect(await
themeItems2.submenu.isVisible()).toBe(true)), keeping the other checks that call
topbar.isMenuItemActive(...) inside toPass; update references to menu,
themeItems2.submenu, topbar.isMenuItemActive, themeItems2.darkTheme, and
themeItems2.lightTheme accordingly.
lint-staged.config.ts (2)

9-20: Prefer a named function declaration for the lint-staged handler.
The inline arrow function can be replaced with a declared function and referenced from the config to align with the repo’s function-style guideline.

♻️ Suggested refactor
 export default {
   'tests-ui/**': () =>
     'echo "Files in tests-ui/ are deprecated. Colocate tests with source files." && exit 1',

   './**/*.js': (stagedFiles: string[]) => formatAndEslint(stagedFiles),

-  './**/*.{ts,tsx,vue,mts}': (stagedFiles: string[]) => {
+  './**/*.{ts,tsx,vue,mts}': typeScriptCommands,
+}
+
+function typeScriptCommands(stagedFiles: string[]) {
     const commands = [...formatAndEslint(stagedFiles), 'pnpm typecheck']

     const hasBrowserTestsChanges = stagedFiles
       .map((f) => path.relative(process.cwd(), f).replace(/\\/g, '/'))
       .some((f) => f.startsWith('browser_tests/'))

     if (hasBrowserTestsChanges) {
       commands.push('pnpm typecheck:browser')
     }

     return commands
-  }
-}
+}

As per coding guidelines, “Do not use function expressions if it's possible to use function declarations instead.”

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

In `@lint-staged.config.ts` around lines 9 - 20, The inline arrow handler for the
lint-staged entry should be replaced with a named function declaration: extract
the block currently assigned to './**/*.{ts,tsx,vue,mts}': (stagedFiles:
string[]) => { ... } into a declared function (e.g., function
lintStagedHandler(stagedFiles: string[])) containing the same logic (commands
creation, hasBrowserTestsChanges detection, push of 'pnpm typecheck:browser'
when appropriate) and then reference that function in the config object instead
of the inline arrow; update any type annotations to match and ensure
formatAndEslint, path, and process.cwd() usages remain unchanged.

10-18: Avoid mutating the commands array.
Build the list immutably to match the repo’s immutability guideline.

♻️ Suggested refactor
-    const commands = [...formatAndEslint(stagedFiles), 'pnpm typecheck']
-
     const hasBrowserTestsChanges = stagedFiles
       .map((f) => path.relative(process.cwd(), f).replace(/\\/g, '/'))
       .some((f) => f.startsWith('browser_tests/'))
-
-    if (hasBrowserTestsChanges) {
-      commands.push('pnpm typecheck:browser')
-    }
-
-    return commands
+    return [
+      ...formatAndEslint(stagedFiles),
+      'pnpm typecheck',
+      ...(hasBrowserTestsChanges ? ['pnpm typecheck:browser'] : [])
+    ]

As per coding guidelines, “Avoid mutable state; prefer immutability and assignment at point of declaration.”

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

In `@lint-staged.config.ts` around lines 10 - 18, The code mutates the commands
array via commands.push; instead, build it immutably at declaration: compute
hasBrowserTestsChanges (from stagedFiles.map(...).some(...)) and then create a
const commands using spreads and a conditional spread to include 'pnpm
typecheck:browser' when needed (e.g., const commands =
[...formatAndEslint(stagedFiles), 'pnpm typecheck', ...(hasBrowserTestsChanges ?
['pnpm typecheck:browser'] : [])]); this avoids mutation of commands and keeps
logic around formatAndEslint, stagedFiles and the conditional inclusion of
typecheck:browser immutable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@browser_tests/fixtures/helpers/NodeOperationsHelper.ts`:
- Around line 36-38: getNodeCount currently calls page.evaluate(() =>
window.app!.graph.nodes.length) which throws if window.app or window.app.graph
is uninitialized; change the evaluate callback to a null-safe access (e.g. check
window.app and window.app.graph and return 0 when missing) mirroring the pattern
used by getGraphNodesCount so the helper becomes resilient during
startup/workflow switches; locate getNodeCount and replace the non‑null
assertion usage inside page.evaluate with a guarded expression that safely
returns a number.

In `@browser_tests/helpers/promotedWidgets.ts`:
- Around line 43-51: The function getPromotedWidgetCount currently returns the
total number of widgets on a node (node?.widgets?.length) which conflicts with
its name; either rename it to getNodeWidgetCount or change its implementation to
count promoted widgets by using getPromotedWidgets(comfyPage, nodeId) and
returning its length. Update callers accordingly and keep references to
getPromotedWidgetCount and getPromotedWidgets (and the proxyWidgets semantics)
so the behavior matches the function name.

In `@browser_tests/tests/dialog.spec.ts`:
- Around line 41-46: Replace the CSS class locator with the centralized test-id
selector: change the declaration using
comfyPage.page.locator('.comfy-missing-nodes') to use the page.getByTestId(...)
helper and the shared selectors map (e.g., selectors.MISSING_NODES or
TEST_IDS.MISSING_NODES exported from fixtures/selectors), adding that test id to
the selectors map if it doesn't exist; update the variable name
(missingNodesWarning) usage unchanged so the rest of the steps (loadWorkflow,
Escape key, visibility assertions) keep working with the new getByTestId
locator.

---

Duplicate comments:
In `@browser_tests/tests/subgraphPromotion.spec.ts`:
- Around line 158-160: Replace the hard-coded test id string in the subgraph
test with the centralized TestIds constant: update the selector in the
subgraphPromotion.spec.ts where you call
subgraphVueNode.getByTestId('subgraph-enter-button') to use the shared TestIds
identifier (e.g., TestIds.SUBGRAPH_ENTER_BUTTON) from
browser_tests/fixtures/selectors.ts and import TestIds at the top if not already
present; keep the variable name enterButton and the getByTestId call otherwise
unchanged.

In `@src/components/rightSidePanel/parameters/SectionWidgets.vue`:
- Around line 78-85: The promotion lookup can miss because widget.sourceNodeId
may be a number; update the call site inside the parents.some block (where
isPromotedWidgetView(widget) leads to promotionStore.isPromoted(...)) to
normalize the source node id to a string (e.g., pass String(widget.sourceNodeId)
or widget.sourceNodeId?.toString()) when calling promotionStore.isPromoted so
the lookup uses the same string-formatted ID as other call sites.

In `@src/components/rightSidePanel/subgraph/SubgraphEditor.vue`:
- Around line 170-195: The bulk promote/demote functions (showAll, hideAll,
showRecommended) update promotionStore but don't trigger a canvas redraw,
leaving the UI stale; after calling promotionStore.promote or
promotionStore.demote in those functions, call the canvas invalidation/redraw
helper (e.g., invoke the existing invalidateCanvas/refreshCanvas/forceRerender
function used elsewhere in this component) once after the loop so the graph
re-renders; ensure you reference showAll, hideAll, showRecommended and
promotionStore.promote/demote and batch the redraw call outside the loops to
avoid excessive repaints.

---

Nitpick comments:
In `@browser_tests/tests/menu.spec.ts`:
- Around line 206-210: The toPass block currently contains auto-retrying
Playwright assertions (expect(menu).toBeVisible(),
expect(themeSubmenu).toBeVisible()) which causes double retrying; change those
to synchronous visibility checks by calling await menu.isVisible() and await
themeSubmenu.isVisible() and assert the booleans (e.g. expect(await
menu.isVisible()).toBe(true)) inside the async callback passed to toPass while
keeping the existing call to await topbar.isMenuItemActive(lightThemeItem) (or
assert its boolean similarly); in short, replace auto-retrying toBeVisible()
calls with .isVisible() checks and simple boolean expectations inside the toPass
block so only the outer retry is exercised.
- Around line 231-238: Inside the toPass wrapper you currently call Playwright's
retrying assertions (await expect(menu).toBeVisible() and await
expect(themeItems2.submenu).toBeVisible()); replace those with non-retrying
checks or move them outside toPass: either await the visibility first (await
expect(menu).toBeVisible(); await expect(themeItems2.submenu).toBeVisible();)
before entering the toPass block, or convert the calls inside toPass to
synchronous checks using isVisible() (e.g., expect(await
menu.isVisible()).toBe(true) and expect(await
themeItems2.submenu.isVisible()).toBe(true)), keeping the other checks that call
topbar.isMenuItemActive(...) inside toPass; update references to menu,
themeItems2.submenu, topbar.isMenuItemActive, themeItems2.darkTheme, and
themeItems2.lightTheme accordingly.

In `@browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts`:
- Around line 21-26: Replace the manual expect.poll(... .count()) checks with
Playwright's built-in locator assertions: use the getByText(...) locator's
.first() and call toBeVisible() so the assertions auto-retry and give better
errors; specifically update the two assertions that use
comfyPage.page.getByText('Error loading image') and
comfyPage.page.getByText('Error loading video') to assert .first().toBeVisible()
instead of polling counts.

In `@lint-staged.config.ts`:
- Around line 9-20: The inline arrow handler for the lint-staged entry should be
replaced with a named function declaration: extract the block currently assigned
to './**/*.{ts,tsx,vue,mts}': (stagedFiles: string[]) => { ... } into a declared
function (e.g., function lintStagedHandler(stagedFiles: string[])) containing
the same logic (commands creation, hasBrowserTestsChanges detection, push of
'pnpm typecheck:browser' when appropriate) and then reference that function in
the config object instead of the inline arrow; update any type annotations to
match and ensure formatAndEslint, path, and process.cwd() usages remain
unchanged.
- Around line 10-18: The code mutates the commands array via commands.push;
instead, build it immutably at declaration: compute hasBrowserTestsChanges (from
stagedFiles.map(...).some(...)) and then create a const commands using spreads
and a conditional spread to include 'pnpm typecheck:browser' when needed (e.g.,
const commands = [...formatAndEslint(stagedFiles), 'pnpm typecheck',
...(hasBrowserTestsChanges ? ['pnpm typecheck:browser'] : [])]); this avoids
mutation of commands and keeps logic around formatAndEslint, stagedFiles and the
conditional inclusion of typecheck:browser immutable.

In `@src/components/rightSidePanel/parameters/WidgetItem.test.ts`:
- Around line 156-203: Add tests covering edge cases for promoted widget
propagation: add one test using createMockPromotedWidgetView with options
undefined or {} and assert stub.props('widget').options is undefined or an empty
object as expected (via mountWidgetItem and StubWidgetComponent), and add
another test that stubs resolvePromotedWidgetSource to return a concrete
promoted source value then mountWidgetItem(createMockPromotedWidgetView(...))
and assert the stub receives the resolved properties (options/type/name/value)
to verify behavior when resolvePromotedWidgetSource returns a defined value.
- Around line 74-95: Replace the unsafe "as unknown as" assertions in the test
helpers with TypeScript's "satisfies" to validate the mock shapes: update
createMockNode to return an object expression that uses "satisfies LGraphNode"
(keeping the overrides: Partial<LGraphNode> and spread) and update
createMockWidget similarly to use "satisfies IBaseWidget" for the widget shape;
this preserves flexible Partial overrides while enabling compile-time shape
checks for createMockNode and createMockWidget.

ℹ️ 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 6b7ed99 and cbb7fcc.

⛔ Files ignored due to path filters (5)
  • browser_tests/tests/nodeSearchBox.spec.ts-snapshots/auto-linked-node-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/nodeSearchBox.spec.ts-snapshots/link-context-menu-search-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/interactions/node/imagePreview.spec.ts-snapshots/vue-node-multiple-promoted-previews-chromium-linux.png is excluded by !**/*.png
  • browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts-snapshots/vue-nodes-upload-widgets-chromium-linux.png is excluded by !**/*.png
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (33)
  • .github/workflows/ci-lint-format.yaml
  • browser_tests/assets/subgraphs/subgraph-with-multiple-promoted-previews.json
  • browser_tests/fixtures/helpers/CommandHelper.ts
  • browser_tests/fixtures/helpers/DragDropHelper.ts
  • browser_tests/fixtures/helpers/NodeOperationsHelper.ts
  • browser_tests/fixtures/helpers/SubgraphHelper.ts
  • browser_tests/fixtures/selectors.ts
  • browser_tests/fixtures/utils/litegraphUtils.ts
  • browser_tests/fixtures/ws.ts
  • browser_tests/helpers/promotedWidgets.ts
  • browser_tests/tests/browserTabTitle.spec.ts
  • browser_tests/tests/changeTracker.spec.ts
  • browser_tests/tests/colorPalette.spec.ts
  • browser_tests/tests/dialog.spec.ts
  • browser_tests/tests/menu.spec.ts
  • browser_tests/tests/sidebar/workflows.spec.ts
  • browser_tests/tests/subgraph.spec.ts
  • browser_tests/tests/subgraphPromotion.spec.ts
  • browser_tests/tests/vueNodes/interactions/node/imagePreview.spec.ts
  • browser_tests/tests/vueNodes/widgets/load/uploadWidgets.spec.ts
  • lint-staged.config.ts
  • src/components/breadcrumb/SubgraphBreadcrumb.vue
  • src/components/graph/DomWidgets.test.ts
  • src/components/graph/DomWidgets.vue
  • src/components/graph/widgets/domWidgetZIndex.test.ts
  • src/components/graph/widgets/domWidgetZIndex.ts
  • src/components/rightSidePanel/RightSidePanel.vue
  • src/components/rightSidePanel/parameters/SectionWidgets.vue
  • src/components/rightSidePanel/parameters/TabSubgraphInputs.vue
  • src/components/rightSidePanel/parameters/WidgetActions.vue
  • src/components/rightSidePanel/parameters/WidgetItem.test.ts
  • src/components/rightSidePanel/parameters/WidgetItem.vue
  • src/components/rightSidePanel/subgraph/SubgraphEditor.vue
💤 Files with no reviewable changes (2)
  • src/components/rightSidePanel/RightSidePanel.vue
  • browser_tests/fixtures/ws.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/components/rightSidePanel/parameters/WidgetActions.vue

@DrJKL DrJKL added the New Browser Test Expectations New browser test screenshot should be set by github action label Feb 23, 2026
@github-actions github-actions bot removed the New Browser Test Expectations New browser test screenshot should be set by github action label Feb 23, 2026
…tors

- getPromotedWidgetCount now delegates to getPromotedWidgets (proxyWidgets) instead of counting all node widgets

- Replace .comfy-missing-nodes CSS locator with data-testid in dialog.spec.ts

- Add missingNodes to TestIds.dialogs in selectors.ts

Amp-Thread-ID: https://ampcode.com/threads/T-019c8bcf-5b2b-7338-83f4-5c5a23b90e85
Co-authored-by: Amp <amp@ampcode.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@src/locales/en/main.json`:
- Around line 1078-1082: Update the two locale strings for consistency: change
"enterSearchAliases" to use a hyphen (“Enter search aliases (comma-separated)”)
and change "unpromoteWidget" to match the promote phrasing by removing the
hyphen and capitalizing consistently (“Unpromote Widget: {name}”), leaving
"promoteWidget" unchanged; update these values in the JSON for the keys
enterSearchAliases and unpromoteWidget.

ℹ️ 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 b1f0ccb and 3a1f242.

📒 Files selected for processing (2)
  • src/lib/litegraph/src/LGraphCanvas.ts
  • src/locales/en/main.json

Comment on lines +1078 to +1082
"enterSearchAliases": "Enter search aliases (comma separated)",
"disconnected": "Disconnected",
"linked": "(Linked)",
"promoteWidget": "Promote Widget: {name}",
"unpromoteWidget": "Un-Promote Widget: {name}"
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

Fix wording consistency in new subgraphStore labels

Line 1078 should be hyphenated, and Line 1082’s “Un-Promote” is inconsistent with “Promote Widget.” Consider “Unpromote” for UI consistency.

💬 Proposed wording tweaks
-    "enterSearchAliases": "Enter search aliases (comma separated)",
+    "enterSearchAliases": "Enter search aliases (comma-separated)",
@@
-    "unpromoteWidget": "Un-Promote Widget: {name}"
+    "unpromoteWidget": "Unpromote Widget: {name}"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/locales/en/main.json` around lines 1078 - 1082, Update the two locale
strings for consistency: change "enterSearchAliases" to use a hyphen (“Enter
search aliases (comma-separated)”) and change "unpromoteWidget" to match the
promote phrasing by removing the hyphen and capitalizing consistently
(“Unpromote Widget: {name}”), leaving "promoteWidget" unchanged; update these
values in the JSON for the keys enterSearchAliases and unpromoteWidget.

Copy link
Collaborator

@AustinMroz AustinMroz left a comment

Choose a reason for hiding this comment

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

Lots of big and scary changes here, but it needs to be merged early into the release cycle. It has my approval.

@DrJKL DrJKL merged commit c25f9a0 into main Feb 23, 2026
47 of 48 checks passed
@DrJKL DrJKL deleted the drjkl/subgraphs-are-the-best branch February 23, 2026 21:33
@DrJKL
Copy link
Contributor Author

DrJKL commented Feb 23, 2026

Lots of big and scary changes here, but it needs to be merged early into the release cycle. It has my approval.

Lots of big and scary changes here, but it needs to be merged early into the release cycle. It has my approval.

YOLO

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants