mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-20 06:20:11 +00:00
## Summary Promoted primitive subgraph inputs (String, Int) render their link anchor at the header position instead of the widget row. Renaming subgraph input labels breaks the match entirely, causing connections to detach from their widgets visually. ## Changes - **What**: Fix widget-input slot positioning for promoted subgraph inputs in both LiteGraph and Vue (Nodes 2.0) rendering modes - `_arrangeWidgetInputSlots`: Removed Vue mode branch that skipped setting `input.pos`. Promoted widget inputs aren't rendered as `<InputSlot>` Vue components (NodeSlots filters them out), so `input.pos` is the only position fallback - `drawConnections`: Added pre-pass to arrange nodes with unpositioned widget-input slots before link rendering. The background canvas renders before the foreground canvas calls `arrange()`, so positions weren't set on the first frame - `SubgraphNode`: Sync `input.widget.name` with the display name on label rename and initial setup. The `IWidgetLocator` name diverged from `PromotedWidgetView.name` after rename, breaking all name-based slot↔widget matching (`_arrangeWidgetInputSlots`, `getWidgetFromSlot`, `getSlotFromWidget`) ## Review Focus - The `_arrangeWidgetInputSlots` rewrite iterates `_concreteInputs` directly instead of building a spread-copy map — simpler and avoids the stale index issue - `input.widget.name` is now kept in sync with the display name (`input.label ?? subgraphInput.name`). This is a semantic shift from using the raw internal name, but it's required for all name-based matching to work after renames. The value is overwritten on deserialize by `_setWidget` anyway - The `_widget` fallback in `_arrangeWidgetInputSlots` is a safety net for edge cases where the name still doesn't match (e.g., stale cache) Fixes #9998 ## Screenshots <img width="847" height="476" alt="Screenshot 2026-03-17 at 3 05 32 PM" src="https://github.com/user-attachments/assets/38f10563-f0bc-44dd-a1a5-f4a7832575d0" /> <img width="804" height="471" alt="Screenshot 2026-03-17 at 3 05 23 PM" src="https://github.com/user-attachments/assets/3237a7ee-f3e5-4084-b330-371def3415bd" /> <img width="974" height="571" alt="Screenshot 2026-03-17 at 3 05 16 PM" src="https://github.com/user-attachments/assets/cafdca46-8d9b-40e1-8561-02cbb25ee8f2" /> <img width="967" height="558" alt="Screenshot 2026-03-17 at 3 05 06 PM" src="https://github.com/user-attachments/assets/fc03ce43-906c-474d-b3bc-ddf08eb37c75" /> ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-10195-fix-subgraph-promoted-widget-input-slot-positions-after-label-rename-3266d73d365081dfa623dd94dd87c718) by [Unito](https://www.unito.io) --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: jaeone94 <jaeone.prt@gmail.com>
46 lines
1.4 KiB
TypeScript
46 lines
1.4 KiB
TypeScript
import type { Page } from '@playwright/test'
|
|
|
|
import type { LGraph, Subgraph } from '../../src/lib/litegraph/src/litegraph'
|
|
import { isSubgraph } from '../../src/utils/typeGuardUtil'
|
|
|
|
/**
|
|
* Assertion helper for tests where being in a subgraph is a precondition.
|
|
* Throws a clear error if the graph is not a Subgraph.
|
|
*/
|
|
export function assertSubgraph(
|
|
graph: LGraph | Subgraph | null | undefined
|
|
): asserts graph is Subgraph {
|
|
if (!isSubgraph(graph)) {
|
|
throw new Error(
|
|
'Expected to be in a subgraph context, but graph is not a Subgraph'
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the widget-input slot Y position and the node title height
|
|
* for the promoted "text" input on the SubgraphNode.
|
|
*
|
|
* The slot Y should be at the widget row, not the header. A value near
|
|
* zero or negative indicates the slot is positioned at the header (the bug).
|
|
*/
|
|
export function getTextSlotPosition(page: Page, nodeId: string) {
|
|
return page.evaluate((id) => {
|
|
const node = window.app!.canvas.graph!.getNodeById(id)
|
|
if (!node) return null
|
|
|
|
const titleHeight = window.LiteGraph!.NODE_TITLE_HEIGHT
|
|
|
|
for (const input of node.inputs) {
|
|
if (!input.widget || input.type !== 'STRING') continue
|
|
return {
|
|
hasPos: !!input.pos,
|
|
posY: input.pos?.[1] ?? null,
|
|
widgetName: input.widget.name,
|
|
titleHeight
|
|
}
|
|
}
|
|
return null
|
|
}, nodeId)
|
|
}
|