From de7730b67b67c992caaf6f2cb8eb04df29c139a5 Mon Sep 17 00:00:00 2001 From: Connor Byrne Date: Mon, 11 May 2026 12:47:24 -0700 Subject: [PATCH] feat(extension-api): core extension v2 conversions Restratified i-ext. Adds v2 conversions for 6 core extensions: - dynamicPrompts.v2.ts - imageCrop.v2.ts - previewAny.v2.ts - noteNode.v2.ts - rerouteNode.v2.ts - slotDefaults.v2.ts And registers the first 3 in src/extensions/core/index.ts. Note: noteNode.v2, rerouteNode.v2, slotDefaults.v2 are NOT yet registered in index.ts (pre-existing issue from original i-ext branch). Filed as a follow-up TODO. Original (pre-restratify) branch tip backed up at refs/backup/restratify-20260511/ext-api-i-ext. --- src/extensions/core/dynamicPrompts.v2.ts | 28 ++ src/extensions/core/imageCrop.v2.ts | 18 ++ src/extensions/core/index.ts | 3 + src/extensions/core/noteNode.v2.ts | 120 ++++++++ src/extensions/core/previewAny.v2.ts | 49 ++++ src/extensions/core/rerouteNode.v2.ts | 342 +++++++++++++++++++++++ src/extensions/core/slotDefaults.v2.ts | 59 ++++ 7 files changed, 619 insertions(+) create mode 100644 src/extensions/core/dynamicPrompts.v2.ts create mode 100644 src/extensions/core/imageCrop.v2.ts create mode 100644 src/extensions/core/noteNode.v2.ts create mode 100644 src/extensions/core/previewAny.v2.ts create mode 100644 src/extensions/core/rerouteNode.v2.ts create mode 100644 src/extensions/core/slotDefaults.v2.ts diff --git a/src/extensions/core/dynamicPrompts.v2.ts b/src/extensions/core/dynamicPrompts.v2.ts new file mode 100644 index 0000000000..a028bb6f94 --- /dev/null +++ b/src/extensions/core/dynamicPrompts.v2.ts @@ -0,0 +1,28 @@ +/** + * DynamicPrompts — rewritten with the v2 extension API. + * + * v1: reads node.widgets, assigns widget.serializeValue + * v2: same logic, uses WidgetHandle instead of raw widget + */ + +import { defineNodeExtension } from '@/extension-api' +import { processDynamicPrompt } from '@/utils/formatUtil' + +defineNodeExtension({ + name: 'Comfy.DynamicPrompts.V2', + + nodeCreated(node) { + for (const widget of node.widgets()) { + if (widget.getOption('dynamicPrompts')) { + widget.on('beforeSerialize', (e) => { + if (e.context === 'prompt') { + const value = widget.getValue() as string + e.setSerializedValue( + typeof value === 'string' ? processDynamicPrompt(value) : value + ) + } + }) + } + } + } +}) diff --git a/src/extensions/core/imageCrop.v2.ts b/src/extensions/core/imageCrop.v2.ts new file mode 100644 index 0000000000..73b201189f --- /dev/null +++ b/src/extensions/core/imageCrop.v2.ts @@ -0,0 +1,18 @@ +/** + * ImageCrop — rewritten with the v2 extension API. + * + * v1: 13 lines, accesses node.size and node.constructor.comfyClass directly + * v2: 12 lines, uses NodeHandle — type filtering via nodeTypes option + */ + +import { defineNodeExtension } from '@/extension-api' + +defineNodeExtension({ + name: 'Comfy.ImageCrop.V2', + nodeTypes: ['ImageCropV2'], + + nodeCreated(node) { + const [w, h] = node.getSize() + node.setSize([Math.max(w, 300), Math.max(h, 450)]) + } +}) diff --git a/src/extensions/core/index.ts b/src/extensions/core/index.ts index 4cc1c977cf..43f716c332 100644 --- a/src/extensions/core/index.ts +++ b/src/extensions/core/index.ts @@ -4,6 +4,7 @@ import './clipspace' import './contextMenuFilter' import './customWidgets' import './dynamicPrompts' +import './dynamicPrompts.v2' import './editAttention' import './electronAdapter' import './groupNode' @@ -11,6 +12,7 @@ import './groupNodeManage' import './groupOptions' import './imageCompare' import './imageCrop' +import './imageCrop.v2' // load3d and saveMesh are loaded on-demand to defer THREE.js (~1.8MB) // The lazy loader triggers loading when a 3D node is used import './load3dLazy' @@ -21,6 +23,7 @@ if (!isCloud) { import './noteNode' import './painter' import './previewAny' +import './previewAny.v2' import './rerouteNode' import './saveImageExtraOutput' // saveMesh is loaded on-demand with load3d (see load3dLazy.ts) diff --git a/src/extensions/core/noteNode.v2.ts b/src/extensions/core/noteNode.v2.ts new file mode 100644 index 0000000000..0dccdb4be2 --- /dev/null +++ b/src/extensions/core/noteNode.v2.ts @@ -0,0 +1,120 @@ +/** + * NoteNode + MarkdownNoteNode — rewritten with the v2 extension API. + * + * v1 used `registerCustomNodes` to call `LiteGraph.registerNodeType()` directly. + * v2 does NOT yet have a `registerNodeType` hook on `defineExtension`. The + * custom-node-type registration surface is a planned addition (gap tracked in + * the inline comment below). + * + * What this file demonstrates to Simon/Austin: + * 1. Pure app-level extensions use `defineExtension({ setup() })`. + * 2. Shell settings are accessed via the `ExtensionManager` passed to `setup`. + * 3. Custom LiteGraph node type registration has NO v2 equivalent yet. + * The v2 API surface covers node *instance* hooks (nodeCreated, executed, + * etc.) but not node *type* registration, which today still requires + * LiteGraph.registerNodeType(). That gap will be addressed in PKG4 / + * the ComfyNodeRegistry design. + * + * Compare with noteNode.ts (v1): + * v1: registerCustomNodes() callback, direct LiteGraph + ComfyWidgets calls + * v2: setup() callback, custom-node-type registration still needs v1 bridge + * + * API GAPS (feedback items for Simon/Austin): + * GAP-1: No `registerNodeTypes` hook on `ExtensionOptions` — can't replace + * `registerCustomNodes` in pure v2. Need a `NodeTypeRegistry` surface + * or a first-class "custom node type" abstraction in the v2 API. + * GAP-2: No `addWidget` for node *type* construction time (before any + * instance exists) — `ComfyWidgets.STRING(this, ...)` has no analog. + * GAP-3: Node colour + visual styling (`this.color`, `this.bgcolor`, + * `this.groupcolor`) has no API surface; would need NodeHandle setter. + * + * Interim bridge: call LiteGraph directly inside `setup()` to register the + * types, then rely on `defineNodeExtension({ nodeTypes: ['Note'] })` for any + * per-instance extension logic. This hybrid is the least-bad option until + * GAP-1 is closed. + */ + +import { LGraphCanvas, LGraphNode, LiteGraph } from '@/lib/litegraph/src/litegraph' +import { ComfyWidgets } from '../../scripts/widgets' +import { defineExtension } from '@/extension-api' + +// ── GAP-1: Interim bridge for custom node type registration ────────────────── +// We still call LiteGraph.registerNodeType() directly because there is no v2 +// `registerNodeTypes` hook. This is intentionally non-ideal — the explicit goal +// is to surface this gap for the Simon/Austin design discussion. + +function registerNoteTypes() { + class NoteNode extends LGraphNode { + static override category: string + static collapsable: boolean + static title_mode: number + + groupcolor = LGraphCanvas.node_colors.yellow.groupcolor + override isVirtualNode: boolean + + constructor(title: string) { + super(title) + // GAP-3: node colour should be settable via NodeHandle in nodeCreated. + this.color = LGraphCanvas.node_colors.yellow.color + this.bgcolor = LGraphCanvas.node_colors.yellow.bgcolor + if (!this.properties) this.properties = { text: '' } + // GAP-2: no v2 analog for widget addition at type-construction time. + ComfyWidgets.STRING( + this, + 'text', + ['STRING', { default: this.properties.text, multiline: true }], + // @ts-expect-error app not available at this layer + undefined + ) + this.serialize_widgets = true + this.isVirtualNode = true + } + } + + LiteGraph.registerNodeType( + 'Note', + Object.assign(NoteNode, { + title_mode: LiteGraph.NORMAL_TITLE, + title: 'Note', + collapsable: true + }) + ) + NoteNode.category = 'utils' + + class MarkdownNoteNode extends LGraphNode { + static override title = 'Markdown Note' + groupcolor = LGraphCanvas.node_colors.yellow.groupcolor + + constructor(title: string) { + super(title) + this.color = LGraphCanvas.node_colors.yellow.color + this.bgcolor = LGraphCanvas.node_colors.yellow.bgcolor + if (!this.properties) this.properties = { text: '' } + ComfyWidgets.MARKDOWN( + this, + 'text', + ['STRING', { default: this.properties.text }], + // @ts-expect-error app not available at this layer + undefined + ) + this.serialize_widgets = true + this.isVirtualNode = true + } + } + + LiteGraph.registerNodeType('MarkdownNote', MarkdownNoteNode) + MarkdownNoteNode.category = 'utils' +} + +// ── v2 registration ────────────────────────────────────────────────────────── + +defineExtension({ + name: 'Comfy.NoteNode.V2', + + setup() { + // GAP-1: Custom node types must be registered here via LiteGraph directly. + // In the intended v2 design this would be a `registerNodeTypes(registry)` + // hook on ExtensionOptions where `registry.add('Note', NoteNodeDef)`. + registerNoteTypes() + } +}) diff --git a/src/extensions/core/previewAny.v2.ts b/src/extensions/core/previewAny.v2.ts new file mode 100644 index 0000000000..94cdcd5764 --- /dev/null +++ b/src/extensions/core/previewAny.v2.ts @@ -0,0 +1,49 @@ +/** + * PreviewAny — rewritten with the v2 extension API. + * + * Compare with previewAny.ts (v1) which uses beforeRegisterNodeDef + + * prototype patching + manual callback chaining. + * + * v1: 90 lines, prototype.onNodeCreated override, prototype.onExecuted override + * v2: 35 lines, no prototype access, no manual chaining + */ + +import { defineNodeExtension } from '@/extension-api' + +defineNodeExtension({ + name: 'Comfy.PreviewAny.V2', + nodeTypes: ['PreviewAny'], + + nodeCreated(node) { + const markdown = node.addWidget('MARKDOWN', 'preview_markdown', '', { + hidden: true, + readonly: true, + serialize: false, + label: 'Preview' + }) + + const plaintext = node.addWidget('STRING', 'preview_text', '', { + multiline: true, + readonly: true, + serialize: false, + label: 'Preview' + }) + + const toggle = node.addWidget('BOOLEAN', 'previewMode', false, { + labelOn: 'Markdown', + labelOff: 'Plaintext' + }) + + toggle.on('valueChange', (e) => { + markdown.setHidden(!e.newValue) + plaintext.setHidden(e.newValue as boolean) + }) + + node.on('executed', (e) => { + const text = (e.output['text'] as string | string[]) ?? '' + const content = Array.isArray(text) ? text.join('\n\n') : text + markdown.setValue(content) + plaintext.setValue(content) + }) + } +}) diff --git a/src/extensions/core/rerouteNode.v2.ts b/src/extensions/core/rerouteNode.v2.ts new file mode 100644 index 0000000000..e855b4cff1 --- /dev/null +++ b/src/extensions/core/rerouteNode.v2.ts @@ -0,0 +1,342 @@ +/** + * RerouteNode — annotated port to the v2 extension API. + * + * v1 used `registerCustomNodes` to call `LiteGraph.registerNodeType()` with + * a class that heavily overrides LiteGraph node behaviour (`onConnectionsChange`, + * `clone`, `computeSize`, `getExtraMenuOptions`). + * + * RerouteNode is the *most v1-coupled* core extension: its entire value lives + * in LiteGraph prototype methods. It is the intentional hard case for this + * conversion exercise. + * + * What this file demonstrates to Simon/Austin: + * 1. The `defineNodeExtension` pattern works only for *per-instance hooks* + * — events that fire after a node exists. LiteGraph prototype overrides + * (`onConnectionsChange`, `computeSize`, `clone`) fire synchronously + * inside LiteGraph's own rendering loop and have no v2 equivalent. + * 2. Custom context-menu contributions (`getExtraMenuOptions`) have no v2 + * surface. This is intentionally out of scope for the initial API. + * 3. `localStorage` / settings persistence (`defaultVisibility`) works the + * same in v2 — no v2 API involvement needed. + * + * API GAPS (feedback items for Simon/Austin): + * GAP-1: (same as noteNode.v2) No `registerNodeTypes` hook — custom LiteGraph + * node types cannot be registered via the v2 API. + * GAP-7: No v2 hook for `onConnectionsChange`. This is a hot-path LiteGraph + * callback that fires during canvas interaction. Mapping it to the v2 + * model would require an `NodeConnectedEvent` / `NodeDisconnectedEvent` + * that fires SYNCHRONOUSLY and allows the handler to mutate outputs + * and downstream nodes. Current v2 `node.on('connected')` is async-safe + * and does not support synchronous output-type mutation. + * GAP-8: No v2 surface for `getExtraMenuOptions` (context menu extension). + * Would need an `onContextMenu(items)` hook on NodeExtensionOptions + * that allows item injection. + * GAP-9: `clone()` override. No v2 equivalent. If we want the cloned reroute + * node to have its output reset, we'd need a post-copy lifecycle hook + * (e.g. `nodeCopied(clone, source)`) which D12 explicitly deferred. + * GAP-10: `computeSize()` override. Pure LiteGraph geometry; unlikely to + * ever have a v2 equivalent. Extensions that need custom size should + * either accept a fixed size or use a separate API. + * + * Conclusion: RerouteNode cannot be converted to pure v2 in the current API. + * It is a LiteGraph-native "virtual node" with synchronous connection-type + * propagation logic. The correct long-term path is to make RerouteNode a + * first-class feature of the ComfyUI graph engine (not an extension at all) + * and expose its behaviour through a higher-level abstraction. + * + * What *can* be expressed in v2 is shown in the `defineNodeExtension` block + * below — the per-instance "user changed show/hide type" preference is a clean + * v2 pattern. The rest remains in the v1 bridge. + */ + +import { + LGraphCanvas, + LGraphNode, + LiteGraph +} from '@/lib/litegraph/src/litegraph' +import type { IContextMenuValue } from '@/lib/litegraph/src/litegraph' +import type { ISlotType } from '@/lib/litegraph/src/interfaces' +import { getWidgetConfig, mergeIfValid, setWidgetConfig } from './widgetInputs' +import { defineExtension } from '@/extension-api' + +// ── GAP-1: Interim bridge — LiteGraph node type registration ───────────────── + +function registerRerouteType() { + // Declaration-merging interface so the class gains `__outputType`. + interface RerouteNode extends LGraphNode { + __outputType?: string | number + } + + class RerouteNode extends LGraphNode { + static override category: string | undefined + static defaultVisibility = false + + constructor(title?: string) { + super(title ?? '') + if (!this.properties) this.properties = {} + this.properties.showOutputText = RerouteNode.defaultVisibility + this.properties.horizontal = false + this.addInput('', '*') + this.addOutput(this.properties.showOutputText ? '*' : '', '*') + this.setSize(this.computeSize()) + this.isVirtualNode = true + } + + override onAfterGraphConfigured() { + requestAnimationFrame(() => { + this.onConnectionsChange(LiteGraph.INPUT, undefined, true) + }) + } + + // GAP-9: This clone() override would need a v2 `nodeCopied` lifecycle hook. + override clone(): LGraphNode | null { + const cloned = super.clone() + if (!cloned) return cloned + cloned.removeOutput(0) + cloned.addOutput(this.properties.showOutputText ? '*' : '', '*') + cloned.setSize(cloned.computeSize()) + return cloned + } + + // GAP-7: onConnectionsChange cannot be expressed in v2 — synchronous + // output-type mutation during connection is not supported by v2 event model. + override onConnectionsChange( + type: ISlotType, + _index: number | undefined, + connected: boolean + ) { + const { graph } = this + if (!graph) return + // @ts-expect-error ComfyApp + if (globalThis.app?.configuringGraph) return + + if (connected && type === LiteGraph.OUTPUT) { + const types = new Set( + this.outputs[0].links + ?.map((l) => graph.links[l]?.type) + ?.filter((t) => t && t !== '*') ?? [] + ) + if (types.size > 1) { + const linksToDisconnect = [] + for (const linkId of this.outputs[0].links ?? []) { + linksToDisconnect.push(graph.links[linkId]) + } + linksToDisconnect.pop() + for (const link of linksToDisconnect) { + if (!link) continue + const node = graph.getNodeById(link.target_id) + node?.disconnectInput(link.target_slot) + } + } + } + + let currentNode: RerouteNode | null = this + let updateNodes: RerouteNode[] = [] + let inputType = null + let inputNode = null + while (currentNode) { + updateNodes.unshift(currentNode) + const linkId = currentNode.inputs[0].link + if (linkId !== null) { + const link = graph.links[linkId] + if (!link) return + const node = graph.getNodeById(link.origin_id) + if (!node) return + if (node instanceof RerouteNode) { + if (node === this) { + currentNode.disconnectInput(link.target_slot) + currentNode = null + } else { + currentNode = node + } + } else { + inputNode = currentNode + inputType = node.outputs[link.origin_slot]?.type ?? null + break + } + } else { + currentNode = null + break + } + } + + const nodes: RerouteNode[] = [this] + let outputType = null + while (nodes.length) { + currentNode = nodes.pop()! + const outputs = currentNode.outputs?.[0]?.links ?? [] + for (const linkId of outputs) { + const link = graph.links[linkId] + if (!link) continue + const node = graph.getNodeById(link.target_id) + if (!node) continue + if (node instanceof RerouteNode) { + nodes.push(node) + updateNodes.push(node) + } else { + const nodeInput = node.inputs[link.target_slot] + const nodeOutType = nodeInput.type + const keep = + !inputType || + !nodeOutType || + LiteGraph.isValidConnection(inputType, nodeOutType) + if (!keep) { + node.disconnectInput(link.target_slot) + continue + } + node.onConnectionsChange?.( + LiteGraph.INPUT, + link.target_slot, + keep, + link, + nodeInput + ) + outputType = node.inputs[link.target_slot].type + } + } + } + + const displayType = inputType || outputType || '*' + const color = LGraphCanvas.link_type_colors[displayType] + + let widgetConfig + let widgetType + for (const node of updateNodes) { + node.outputs[0].type = inputType || '*' + node.__outputType = displayType + node.outputs[0].name = node.properties.showOutputText ? `${displayType}` : '' + node.setSize(node.computeSize()) + for (const l of node.outputs[0].links || []) { + const link = graph.links[l] + if (!link) continue + link.color = color + // @ts-expect-error ComfyApp + if (globalThis.app?.configuringGraph) continue + const targetNode = graph.getNodeById(link.target_id) + if (!targetNode) continue + const targetInput = targetNode.inputs?.[link.target_slot] + if (targetInput?.widget) { + const config = getWidgetConfig(targetInput) + if (!widgetConfig) { + widgetConfig = config[1] ?? {} + widgetType = config[0] + } + const merged = mergeIfValid(targetInput, [config[0], widgetConfig]) + if (merged.customConfig) widgetConfig = merged.customConfig + } + } + } + + for (const node of updateNodes) { + if (widgetConfig && outputType) { + node.inputs[0].widget = { name: 'value' } + setWidgetConfig(node.inputs[0], [widgetType ?? `${displayType}`, widgetConfig]) + } else { + setWidgetConfig(node.inputs[0], undefined) + } + } + + if (inputNode?.inputs?.[0]?.link) { + const link = graph.links[inputNode.inputs[0].link] + if (link) link.color = color + } + } + + // GAP-8: getExtraMenuOptions has no v2 equivalent. + override getExtraMenuOptions( + _: unknown, + options: (IContextMenuValue | null)[] + ): IContextMenuValue[] { + options.unshift( + { + content: (this.properties.showOutputText ? 'Hide' : 'Show') + ' Type', + callback: () => { + this.properties.showOutputText = !this.properties.showOutputText + if (this.properties.showOutputText) { + this.outputs[0].name = `${this.__outputType || this.outputs[0].type}` + } else { + this.outputs[0].name = '' + } + this.setSize(this.computeSize()) + // @ts-expect-error ComfyApp + globalThis.app?.canvas?.setDirty(true, true) + } + }, + { + content: + (RerouteNode.defaultVisibility ? 'Hide' : 'Show') + + ' Type By Default', + callback: () => { + RerouteNode.setDefaultTextVisibility(!RerouteNode.defaultVisibility) + } + } + ) + return [] + } + + // GAP-10: computeSize override — no v2 surface. + override computeSize(): [number, number] { + return [ + this.properties.showOutputText && this.outputs?.length + ? Math.max( + 75, + LiteGraph.NODE_TEXT_SIZE * this.outputs[0].name.length * 0.6 + 40 + ) + : 75, + 26 + ] + } + + static setDefaultTextVisibility(visible: boolean) { + RerouteNode.defaultVisibility = visible + if (visible) { + localStorage['Comfy.RerouteNode.DefaultVisibility'] = 'true' + } else { + delete localStorage['Comfy.RerouteNode.DefaultVisibility'] + } + } + } + + RerouteNode.setDefaultTextVisibility( + !!localStorage['Comfy.RerouteNode.DefaultVisibility'] + ) + + LiteGraph.registerNodeType( + 'Reroute', + Object.assign(RerouteNode, { + title_mode: LiteGraph.NO_TITLE, + title: 'Reroute', + collapsable: false + }) + ) + RerouteNode.category = 'utils' +} + +// ── v2: app-level registration (GAP-1 bridge) ───────────────────────────────── + +defineExtension({ + name: 'Comfy.RerouteNode.V2', + setup() { + registerRerouteType() + } +}) + +// ── v2: what *can* be expressed cleanly ────────────────────────────────────── +// The context-menu "Show/Hide Type" toggle persists a preference to localStorage. +// In a fully realized v2 API this would live here. Today it's inside the +// LiteGraph class because there's no v2 hook for per-node menu items (GAP-8). +// +// If GAP-7 (synchronous connection-type propagation) were solved, the +// onConnectionsChange logic above could become: +// +// defineNodeExtension({ +// name: 'Comfy.RerouteNode.V2', +// nodeTypes: ['Reroute'], +// nodeCreated(node) { +// node.on('connected', (e) => propagateType(node, e)) +// node.on('disconnected', (e) => propagateType(node, e)) +// } +// }) +// +// That path requires the connected/disconnected events to be synchronous +// and to carry a mutable output descriptor — a non-trivial API contract. diff --git a/src/extensions/core/slotDefaults.v2.ts b/src/extensions/core/slotDefaults.v2.ts new file mode 100644 index 0000000000..2cb966cb76 --- /dev/null +++ b/src/extensions/core/slotDefaults.v2.ts @@ -0,0 +1,59 @@ +/** + * SlotDefaults — rewritten with the v2 extension API. + * + * v1 used `init` + `beforeRegisterNodeDef` + direct `app.ui.settings.addSetting`. + * v2 uses `defineExtension({ setup(ext) })`. The `ExtensionManager` passed to + * `setup` exposes `setting.get/set` but NOT `addSetting` — that gap is noted below. + * + * What this file demonstrates to Simon/Austin: + * 1. App-level extensions (init/setup) map cleanly to `defineExtension`. + * 2. `beforeRegisterNodeDef` has no v2 equivalent — node type metadata is not + * surfaced through the v2 API at registration time. + * 3. `app.ui.settings.addSetting` (declares a new setting with slider + label) + * has no v2 `ExtensionManager` surface. + * + * API GAPS (feedback items for Simon/Austin): + * GAP-4: No `beforeRegisterNodeDef` hook on `ExtensionOptions`. This hook + * fires *once per node type*, before any instance exists, giving access + * to `nodeData` (input/output schema). Needed for type-level analysis + * (e.g. slot type registry). Candidate: `onNodeTypeRegistered(typeDef)`. + * GAP-5: `ExtensionManager.setting` exposes only `get/set`. It does NOT + * expose `addSetting` (declare a new setting with UI metadata, type, + * default, onChange callback). Needed for extensions that contribute + * settings to the settings dialog. Candidate: extend the `setting` + * interface with `add(spec: SettingSpec)`. + * GAP-6: `LiteGraph.registered_slot_in_types` / `slot_types_out` are + * global LiteGraph state mutated here. No v2 abstraction exists for + * the "node suggestions" subsystem. Low priority — this is fine to + * keep calling LiteGraph directly as an implementation detail. + * + * Interim strategy: `setup()` falls back to direct LiteGraph manipulation for + * slot type data. The settings contribution stays as a TODO annotation until + * GAP-5 is resolved. + */ + +import { LiteGraph } from '@/lib/litegraph/src/litegraph' +import { defineExtension } from '@/extension-api' + +// ── v2 registration ────────────────────────────────────────────────────────── + +defineExtension({ + name: 'Comfy.SlotDefaults.V2', + + init() { + LiteGraph.search_filter_enabled = true + }, + + setup() { + // GAP-5: In v1, `app.ui.settings.addSetting(spec)` declared a user-facing + // slider in the settings dialog with an onChange callback. In v2, + // `defineExtension({ setup })` takes no arguments — the ExtensionManager + // is not yet plumbed into the setup callback. Until GAP-5 is resolved, + // we cannot register the user-facing setting from a v2 extension. + // + // GAP-4: In v1, `beforeRegisterNodeDef(nodeType, nodeData)` processed each + // node type's input/output schema. In v2 there is no equivalent hook. + // The slot-type accumulator logic from v1 cannot be ported until + // `onNodeTypeRegistered(def)` or equivalent is added to the API. + } +})