Commit Graph

8 Commits

Author SHA1 Message Date
DrJKL
0175ab9aa2 refactor: share widget grid types and slim processed widgets
Extract WidgetGridItem and WidgetSlotMetadata into a shared types module and
make ProcessedWidget extend it, dropping the dead flat fields it duplicated.
Decompose computeProcessedWidgets into resolveLiveWidgetContext + processWidget,
dedupe ids via a Set, and stop exporting isWidgetVisible/hasWidgetError (tested
through the public compute path instead).

WidgetGrid drives widgets through :model-value so updateHandler is the sole
writer; preview items stay static. removeWidget now drops a widget from the
render order without purging its stored value, so remove-then-re-add restores
what the user set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 12:36:01 -07:00
DrJKL
003dcea90a refactor: drop dead widget spec store and replaceNodeWidgetOrder
The graphWidgetSpecs map, WidgetSpec type, registerWidgetSpec/getWidgetSpec,
and replaceNodeWidgetOrder had no production callers: specs always fell
through to nodeDefStore.getInputSpecForWidget, and no path pruned via
replaceNodeWidgetOrder. Also drop the unused getNodeWidgetIds litegraph
util and an inert File-array branch in normalizeWidgetValue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 12:36:01 -07:00
DrJKL
5b065a299e fix: sync widget order through store 2026-07-06 12:34:28 -07:00
Alexander Brown
b4ae6344d7 Brand local node IDs (#13085)
## Summary

Adds a branded local `NodeId` helper and starts separating local node
identity from serialized workflow IDs.

## Changes

- **What**: Adds central `NodeId` parsing/branding helpers, migrates
nearby widget identity types, keeps queue results at the serialized
boundary, and removes misleading workflow `NodeId` usage from execution
error maps.

## Review Focus

Check that the first migration slice keeps serialized/API IDs as raw
`number | string` while local UI/store IDs use the branded string type.

## Caveat

`SUBGRAPH_INPUT_ID` and `SUBGRAPH_OUTPUT_ID` are now branded local
`NodeId` string values internally instead of numeric sentinels.
Reviewers should double-check extension compatibility for callers that
import `Constants` and compare those values numerically.

## Screenshots (if applicable)

N/A

---------

Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: AustinMroz <austin@comfy.org>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-26 22:54:04 +00:00
Alexander Brown
e994e4df58 refactor: store-backed WidgetId subgraph host widgets; delete widgetValueIO layer (#12617)
## Summary

Make `WidgetId` (`graphId:nodeId:name`) the single canonical widget
identity and represent subgraph promoted host widgets as ordinary
store-backed widgets addressed by it. This deletes two whole indirection
layers — the `world/*` widget-entity-IO layer and the
`PromotedWidgetView` runtime — leaving one model: a widget's data lives
in `widgetValueStore` keyed by `WidgetId`, and a `SubgraphNode` input
references it via `input.widgetId`.

**Net +304 lines across 107 files** (5,044 added / 4,740 deleted):
production code is net **−798** (1,521 added / 2,319 deleted) while
tests are net **+1,102** (3,523 added / 2,421 deleted). 10 files deleted
outright, 14 added.

## What got deleted

The old design wrapped every promoted subgraph widget in a synthetic
`IBaseWidget` "view" object with live getters that followed the source
widget, plus a manager to keep view identities stable, plus an IO
indirection layer over the store. All of it is gone:

- `promotedWidgetView.ts` — the `PromotedWidgetView` class (draw /
pointer / DOM-sync / projection / deepest-source resolution getters)
- `PromotedWidgetViewManager.ts` — view reconciliation/caching
- `world/widgetValueIO.ts` — the IO wrapper over `widgetValueStore`
- `world/entityIds.ts` + `world/brand.ts` — the `WidgetEntityId`
branded-id layer and the `entityId` field
- `widgetNodeTypeGuard.ts` — only used by the deleted view
- the per-`SubgraphNode` view machinery (`_promotedViewManager`,
`_cacheVersion`, view-key generation, DOM position-override cleanup) and
every now-dead `isPromotedWidgetView` branch across the panel, menu,
store, and util consumers
- `domWidgetStore` position-override APIs (`setPositionOverride` /
`clearPositionOverride`), only used to render a promoted DOM widget on a
different host node

## Why it's simpler

- One source of truth. A promoted host widget is `WidgetState` in the
store, seeded from the source at promotion (`registerWidget` with a
deep-cloned snapshot) and independent thereafter. No synthetic widget
objects, no runtime source-following, no view cache to invalidate.
- Resolution is data-driven. `resolveConcretePromotedWidget` walks
`SubgraphNode` inputs (`input.widgetId` + `resolveSubgraphInputTarget`)
instead of chasing view objects through `node.widgets`. This also
**fixes two-layer nested promotion** — the previously-skipped parity
test now passes and resolves through to the deepest concrete widget.
- The right-panel Parameters tab renders a subgraph node's promoted
widgets through the **same** store-backed path as ordinary node widgets:
display reads `WidgetState` via `widget.widgetId`, and value writes go
through `widgetValueStore.setValue(widgetId)`.

## Changes

- **What**:
- `WidgetId` branded type + `widgetId()` / `parseWidgetId()` /
`isWidgetId()` and a `WidgetState` type; `widgetValueStore` is
`WidgetId`-native (`registerWidget` / `getWidget` / `setValue` /
`deleteWidget`).
- Promotion creates host `WidgetState` then an input projection
(`input.widgetId`); demotion clears it; serialization and legacy
`proxyWidgets` migration round-trip through `input.widgetId`.
- `promotedInputWidget.ts` projects a store-backed ordinary widget from
an input slot; `SubgraphNode.widgets` is now a projected getter over
inputs (kept Litegraph-shaped so the canvas renderer and extensions
still read `node.widgets`). `invalidatePromotedViews()` is retained as a
no-op for extension compatibility.
- `promotedWidgetControl.ts` applies `control_after_generate` (e.g. seed
increment) on the host node, since the interior control widget is
link-fed and its value is dead; `syncPromotedComboHostOptions` mirrors
interior combo options onto host state.
- `multilineTextarea.ts` extracts the reusable multiline DOM-widget
behavior out of `useStringWidget` and adds promoted multiline
materialization via a `createPromotedHostWidget` app-layer hook (keeping
Litegraph core free of Vue/Pinia/DOM).
- Late-bound `LiteGraph` singleton holder (`litegraphInstance.ts`) to
break a widget-init import cycle.
- **Breaking**:
  - `IBaseWidget.entityId` removed — use `widgetId`.
- `SubgraphNode.widgets` no longer exposes the old `PromotedWidgetView`
objects; promoted state lives in `widgetValueStore` keyed by
`input.widgetId` and `widgets` is a projection of inputs. The
`widget-promoted` event now carries the concrete interior widget.
Extension code reading `entityId` or relying on `PromotedWidgetView` is
affected.

## Review Focus

- Promotion / demotion / serialization round-tripping through
`input.widgetId` + `widgetValueStore`, incl. the legacy `proxyWidgets`
migration.
- Snapshot-at-promotion semantics (host widget does not follow the
source after creation), and the combo-options exception via
`syncPromotedComboHostOptions`.
- Two-layer nested resolution in `resolveConcretePromotedWidget` +
`SubgraphNode` nested-source resolution.
- The unified Parameters tab (`TabSubgraphInputs` → `SectionWidgets`):
value edit / rename / favorite / hide / reorder for promoted inputs are
wired through the store but warrant a visual/e2e pass.
- Litegraph-compat seams worth a careful read: projected
`SubgraphNode.widgets`, the canvas-edit `callback` bridge back to the
store, host-level `control_after_generate`, and the late-bound
`LiteGraph` holder / `domWidget.ts` import ordering.

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: AustinMroz <austin@comfy.org>
Co-authored-by: GitHub Action <action@github.com>
2026-06-16 23:19:12 +00:00
Alexander Brown
e566ec4ca3 refactor: relocate UUID and NodeId out of litegraph (#12581)
## Summary

Move the canonical `UUID` utilities and the `NodeId` alias up out of
`src/lib/litegraph/` so non-litegraph code can reference them without
crossing the litegraph layer boundary.

## Changes

- **What**:
- `src/lib/litegraph/src/utils/uuid.ts` → `src/utils/uuid.ts` (full
file: `UUID`, `zeroUuid`, `createUuidv4`).
- `NodeId` moves from `src/lib/litegraph/src/LGraphNode.ts` to
`src/world/entityIds.ts`. `LGraphNode.ts` re-exports it; the litegraph
barrel still re-exports `createUuidv4` / `UUID` so the package's public
surface is unchanged.
- All 22 importers updated to `@/utils/uuid` (both
`@/lib/litegraph/src/utils/uuid` and the litegraph-internal
`./utils/uuid` relative paths).
- Drops the two `import-x/no-restricted-paths` ESLint disables in
`src/world/entityIds.ts` that were waiting on these moves.
- **Breaking**: None — litegraph re-exports preserve backward
compatibility for downstream consumers.

## Review Focus

- Each importer's change is identical (`@/lib/litegraph/src/utils/uuid`
→ `@/utils/uuid`), generated by `sed`.
- `src/lib/litegraph/src/LGraphNode.ts` now does `import type { NodeId }
from '@/world/entityIds'` + `export type { NodeId }` — confirm this
satisfies the litegraph layer boundary rules.
- `src/world/entityIds.ts` defines `NodeId` locally as `number |
string`; no semantic change.

Co-authored-by: Amp <amp@ampcode.com>
2026-06-02 03:11:15 +00:00
Alexander Brown
c25f9a0e93 feat: synthetic widgets getter for SubgraphNode (proxy-widget-v2) (#8856)
## 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)

<!-- Fixes #ISSUE_NUMBER -->

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8856-feat-synthetic-widgets-getter-for-SubgraphNode-proxy-widget-v2-3076d73d365081c7b517f5ec7cb514f3)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: GitHub Action <action@github.com>
2026-02-23 13:33:41 -08:00
Alexander Brown
a7c2115166 feat: add WidgetValueStore for centralized widget value management (#8594)
## Summary

Implements Phase 1 of the **Vue-owns-truth** pattern for widget values.
Widget values are now canonical in a Pinia store; `widget.value`
delegates to the store while preserving full backward compatibility.

## Changes

- **New store**: `src/stores/widgetValueStore.ts` - centralized widget
value storage with `get/set/remove/removeNode` API
- **BaseWidget integration**: `widget.value` getter/setter now delegates
to store when widget is associated with a node
- **LGraphNode wiring**: `addCustomWidget()` automatically calls
`widget.setNodeId(this.id)` to wire widgets to their nodes
- **Test fixes**: Added Pinia setup to test files that use widgets

## Why

This foundation enables:
- Vue components to reactively bind to widget values via `computed(() =>
store.get(...))`
- Future Yjs/CRDT backing for real-time collaboration
- Cleaner separation between Vue state and LiteGraph rendering

## Backward Compatibility

| Extension Pattern | Status |
|-------------------|--------|
| `widget.value = x` |  Works unchanged |
| `node.widgets[i].value` |  Works unchanged |
| `widget.callback` |  Still fires |
| `node.onWidgetChanged` |  Still fires |

## Testing

-  4252 unit tests pass
-  Build succeeds

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-8594-feat-add-WidgetValueStore-for-centralized-widget-value-management-2fc6d73d36508160886fcb9f3ebd941e)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: GitHub Action <action@github.com>
2026-02-10 19:37:17 -08:00