mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-27 16:34:40 +00:00
## Summary Introduces **Subgraph Link Only Promotion** (ADR 0009) — a new model for surfacing inner subgraph widgets on the parent SubgraphNode by *promoting through links* rather than by duplicating widget state on the host. Ships with the hygiene/refactor pass on the migration, store, and event layers that the new model depends on. ## What changes ### Subgraph Link Only Promotion (ADR 0009) Promoted widgets are defined by the link from a SubgraphNode input to the interior node, not by a duplicated widget instance on the host. Consequences: - A SubgraphNode renders inner widgets purely as a **projection** of the interior widgets and links — no host-side state to drift. - **Per-host independence**: multiple instances of the same SubgraphNode render and edit their own values without cross-talk. - **Reversible promote/demote**: structural link operation, so demote preserves host slots and external connections (#12278). ### Supporting refactors - **Migration** — Planner/classifier/repair/quarantine helpers collapsed into a single `proxyWidgetMigration` entry point with black-box round-trip coverage. Honors the source-node-id disambiguator on `proxyWidgets`, so deduplicated names (e.g. `text`, `text_1`) resolve to the right interior widget. - **Widget identity** — `appMode` unified on `WidgetEntityId`; promoted widget state is keyed by entityId across the store, DOM, and migration paths. - **SubgraphNode** — 3-key promoted-view cache replaced with a single version counter + explicit `invalidatePromotedViews()` at mutation sites; `id === -1` sentinel removed. - **Events** — `LGraph.trigger()` now dispatches node trigger payloads through `this.events`, replacing a leaky `onTrigger` monkey-patch. `SubgraphEditor` reactivity is driven from subgraph events instead of imperative refresh. - **Stores** — `appModeStore` migration helpers collapsed into `upgradeAndValidateInput`; `nodeOutputStore.*ByExecutionId` derived from the locator index; `previewExposureStore` cleanup and cycle-detection double-warn fix. - **Misc** — `Outcome` types consolidated; mutable accumulators replaced with `flatMap`; new ESLint rule forbids litegraph imports under `src/world/`. ### Tests - Browser tests for promoted widgets retagged `@vue-nodes` and rewritten to assert against the rendered Vue node DOM (via `getNodeLocator` / `getByRole('textbox')` / `enterSubgraph`) instead of `page.evaluate` graph introspection. - Per-host widget independence asserted via DOM. - Migration coverage moved to black-box round-trip tests. - Added coverage for duplicate-named promoted widget identity (ADR 0009) and the per-parent demote branch in `WidgetActions`. ## Review focus - ADR 0009 conformance of the link-only promotion model. - Disambiguator resolution path in `proxyWidgetMigration`. - Single-version-counter promoted-view cache and its `invalidatePromotedViews()` call sites. - `LGraph.trigger()` event dispatch and the `AppModeWidgetList.vue` migration off `onTrigger` (FE-667 tracks the remaining `useGraphNodeManager` conversion). ## Breaking changes None for users. Internal subgraph promotion APIs changed — see ADR 0009. ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12197-feat-subgraph-link-only-widget-promotion-migration-store-hygiene-35e6d73d365081fd882cf3a69bc09956) by [Unito](https://www.unito.io) --------- Co-authored-by: Amp <amp@ampcode.com> Co-authored-by: GitHub Action <action@github.com> Co-authored-by: github-actions <github-actions@github.com> Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com> Co-authored-by: AustinMroz <austin@comfy.org>
73 lines
1.7 KiB
Vue
73 lines
1.7 KiB
Vue
<script setup lang="ts">
|
|
import { computed } from 'vue'
|
|
|
|
import Button from '@/components/ui/button/Button.vue'
|
|
import { cn } from '@comfyorg/tailwind-utils'
|
|
import type { ClassValue } from '@comfyorg/tailwind-utils'
|
|
|
|
const {
|
|
nodeTitle,
|
|
widgetName,
|
|
isDraggable = false,
|
|
isPhysical = false,
|
|
isShown = false,
|
|
class: className
|
|
} = defineProps<{
|
|
nodeTitle: string
|
|
widgetName: string
|
|
isDraggable?: boolean
|
|
isPhysical?: boolean
|
|
isShown?: boolean
|
|
class?: ClassValue
|
|
}>()
|
|
defineEmits<{ toggleVisibility: [] }>()
|
|
|
|
const icon = computed(() =>
|
|
isPhysical
|
|
? 'icon-[lucide--link]'
|
|
: isShown
|
|
? 'icon-[lucide--eye]'
|
|
: 'icon-[lucide--eye-off]'
|
|
)
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
:class="
|
|
cn(
|
|
'flex items-center gap-1 rounded-sm px-2 py-1 break-all',
|
|
'bg-node-component-surface',
|
|
isDraggable && 'ring-accent-background hover:ring-1',
|
|
className
|
|
)
|
|
"
|
|
data-testid="subgraph-widget-item"
|
|
>
|
|
<div class="pointer-events-none flex-1">
|
|
<div
|
|
class="line-clamp-1 text-xs text-text-secondary"
|
|
data-testid="subgraph-widget-node-name"
|
|
>
|
|
{{ nodeTitle }}
|
|
</div>
|
|
<div class="line-clamp-1 text-sm/8" data-testid="subgraph-widget-label">
|
|
{{ widgetName }}
|
|
</div>
|
|
</div>
|
|
<Button
|
|
variant="muted-textonly"
|
|
size="sm"
|
|
data-testid="subgraph-widget-toggle"
|
|
:disabled="isPhysical"
|
|
@click.stop="$emit('toggleVisibility')"
|
|
>
|
|
<i :class="icon" :data-testid="isPhysical ? 'icon-link' : 'icon-eye'" />
|
|
</Button>
|
|
<div
|
|
v-if="isDraggable"
|
|
data-testid="subgraph-widget-drag-handle"
|
|
class="pointer-events-none icon-[lucide--grip-vertical] size-4"
|
|
/>
|
|
</div>
|
|
</template>
|