diff --git a/src/extension-api-v2/harness.ts b/src/extension-api-v2/harness.ts index 54cd623cab..c8699d3c44 100644 --- a/src/extension-api-v2/harness.ts +++ b/src/extension-api-v2/harness.ts @@ -1,19 +1,16 @@ /** - * Test harness for v1/v2 extension compatibility tests. - * Provides minimal World + MiniGraph + MiniComfyApp stubs for proof-of-concept tests. + * Test harness stubs for v1 contract tests. * - * Phase A limitation: These are minimal stubs. Phase B will provide a full - * eval sandbox + LiteGraph prototype wiring for real behavioral tests. + * Phase A limitation: These are minimal stubs to make tests compile. + * Real implementations land with Phase B (ECS substrate + eval sandbox). */ -type NodeEntityId = string +import type { NodeEntityId } from '@/world/entityIds' interface HarnessWorld { findNode(id: NodeEntityId): { type: string } | undefined - allNodes(): NodeEntityId[] + allNodes(): { type: string }[] clear(): void - _addNode(id: NodeEntityId, data: { type: string }): void - _removeNode(id: NodeEntityId): void } interface MiniGraph { @@ -25,69 +22,51 @@ interface MiniComfyApp { graph: MiniGraph } -/** - * Creates a minimal harness World for testing. - */ -export function createHarnessWorld(): HarnessWorld { - const nodes = new Map() +const nodes = new Map() +let nodeCounter = 0 +export function createHarnessWorld(): HarnessWorld { + nodes.clear() + nodeCounter = 0 return { findNode(id: NodeEntityId) { return nodes.get(id) }, allNodes() { - return [...nodes.keys()] + return [...nodes.values()] }, clear() { nodes.clear() - }, - _addNode(id: NodeEntityId, data: { type: string }) { - nodes.set(id, data) - }, - _removeNode(id: NodeEntityId) { - nodes.delete(id) } } } -/** - * Creates a minimal MiniComfyApp for testing. - * The app's graph operations are wired to the provided world. - */ -export function createMiniComfyApp(world: HarnessWorld): MiniComfyApp { - let idCounter = 0 - +export function createMiniComfyApp(_world: HarnessWorld): MiniComfyApp { return { graph: { add(opts: { type: string }): NodeEntityId { - const id = `node:${++idCounter}` as NodeEntityId - world._addNode(id, { type: opts.type }) + const id = (++nodeCounter) as unknown as NodeEntityId + nodes.set(id, { type: opts.type }) return id }, - remove(id: NodeEntityId) { - world._removeNode(id) + remove(id: NodeEntityId): void { + nodes.delete(id) } } } } -// Evidence snapshot loading stubs for S2.* surface coverage -const evidenceSnapshots: Record = { +// Evidence snippet loading — stubs for I-TF.3.C3 POC +const evidenceSnippets: Record = { 'S2.N4': [ - '// LTXVideo sparse_track_editor.js:137\nnode.onRemoved = function() { cleanup(); }' + 'node.onRemoved = function() { clearInterval(this._interval); this._element?.remove(); }' ] } -/** - * Returns the number of evidence excerpts for a given surface ID. - */ export function countEvidenceExcerpts(surfaceId: string): number { - return evidenceSnapshots[surfaceId]?.length ?? 0 + return evidenceSnippets[surfaceId]?.length ?? 0 } -/** - * Loads a specific evidence snippet by surface ID and index. - */ export function loadEvidenceSnippet(surfaceId: string, index: number): string { - return evidenceSnapshots[surfaceId]?.[index] ?? '' + return evidenceSnippets[surfaceId]?.[index] ?? '' } diff --git a/src/extensions/core/rerouteNode.v2.ts b/src/extensions/core/rerouteNode.v2.ts index d6fd4db664..e855b4cff1 100644 --- a/src/extensions/core/rerouteNode.v2.ts +++ b/src/extensions/core/rerouteNode.v2.ts @@ -57,11 +57,12 @@ import { import type { IContextMenuValue } from '@/lib/litegraph/src/litegraph' import type { ISlotType } from '@/lib/litegraph/src/interfaces' import { getWidgetConfig, mergeIfValid, setWidgetConfig } from './widgetInputs' -import { defineExtension, defineNodeExtension } from '@/extension-api' +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 } @@ -339,15 +340,3 @@ defineExtension({ // // That path requires the connected/disconnected events to be synchronous // and to carry a mutable output descriptor — a non-trivial API contract. - -defineNodeExtension({ - name: 'Comfy.RerouteNode.V2', - nodeTypes: ['Reroute'], - - nodeCreated(_node) { - // Currently a no-op: all meaningful behaviour is inside the LiteGraph - // class due to GAP-7, GAP-8, GAP-9, GAP-10. This block is here to show - // that node-instance hooks work fine and will be filled in once the API - // surface closes the gaps above. - } -}) diff --git a/src/extensions/core/slotDefaults.v2.ts b/src/extensions/core/slotDefaults.v2.ts index 2ab1919aac..2cb966cb76 100644 --- a/src/extensions/core/slotDefaults.v2.ts +++ b/src/extensions/core/slotDefaults.v2.ts @@ -33,91 +33,7 @@ */ import { LiteGraph } from '@/lib/litegraph/src/litegraph' -import { ComfyWidgets } from '../../scripts/widgets' import { defineExtension } from '@/extension-api' -import type { ExtensionManager } from '@/extension-api' - -// Type of the slot-type accumulator (mirrors v1's extension-instance fields). -interface SlotTypeAccumulator { - slot_types_default_out: Record - slot_types_default_in: Record -} - -const acc: SlotTypeAccumulator = { - slot_types_default_out: {}, - slot_types_default_in: {} -} - -let maxSuggestions: number = 5 - -function applyDefaults() { - LiteGraph.slot_types_default_out = {} - LiteGraph.slot_types_default_in = {} - for (const type in acc.slot_types_default_out) { - LiteGraph.slot_types_default_out[type] = acc.slot_types_default_out[ - type - ].slice(0, maxSuggestions) - } - for (const type in acc.slot_types_default_in) { - LiteGraph.slot_types_default_in[type] = acc.slot_types_default_in[ - type - ].slice(0, maxSuggestions) - } -} - -// GAP-4: In v1 this was `beforeRegisterNodeDef(nodeType, nodeData)`. -// In v2 there is no equivalent hook. This function is called manually during -// setup from a hypothetical type registry iteration — shown here as a stub to -// make the shape visible to Simon/Austin. -function processNodeDef( - nodeId: string, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - nodeData: { input?: { required?: Record }; output?: string[] }, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - nodeType: { comfyClass?: string } -) { - const inputs = nodeData.input?.required ?? {} - for (const inputKey in inputs) { - const input = inputs[inputKey] - if (typeof input[0] !== 'string') continue - const type = input[0] as string - if (type in ComfyWidgets) { - const customProperties = input[1] as Record | undefined - if (!customProperties?.forceInput) continue - } - if (!(type in acc.slot_types_default_out)) { - acc.slot_types_default_out[type] = ['Reroute'] - } - if (!acc.slot_types_default_out[type].includes(nodeId)) { - acc.slot_types_default_out[type].push(nodeId) - } - const lowerType = type.toLocaleLowerCase() - if (!(lowerType in LiteGraph.registered_slot_in_types)) { - LiteGraph.registered_slot_in_types[lowerType] = { nodes: [] } - } - LiteGraph.registered_slot_in_types[lowerType].nodes.push( - nodeType.comfyClass ?? nodeId - ) - } - for (const el of nodeData.output ?? []) { - const type = el - if (!(type in acc.slot_types_default_in)) { - acc.slot_types_default_in[type] = ['Reroute'] - } - if (!acc.slot_types_default_in[type].includes(nodeId)) { - acc.slot_types_default_in[type].push(nodeId) - } - if (!(type in LiteGraph.registered_slot_out_types)) { - LiteGraph.registered_slot_out_types[type] = { nodes: [] } - } - // @ts-expect-error comfyClass - LiteGraph.registered_slot_out_types[type].nodes.push(nodeType.comfyClass ?? nodeId) - if (!LiteGraph.slot_types_out.includes(type)) { - LiteGraph.slot_types_out.push(type) - } - } - applyDefaults() -} // ── v2 registration ────────────────────────────────────────────────────────── @@ -128,31 +44,16 @@ defineExtension({ LiteGraph.search_filter_enabled = true }, - setup(_ext: ExtensionManager) { + setup() { // GAP-5: In v1, `app.ui.settings.addSetting(spec)` declared a user-facing - // slider in the settings dialog with an onChange callback that called - // applyDefaults(). In v2, `ExtensionManager.setting` only exposes get/set - // — there is no `addSetting`. Until GAP-5 is resolved, we read the setting - // value but cannot declare the setting UI. + // 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. // - // Desired v2 code (not yet compilable): - // ext.setting.add({ - // id: 'Comfy.NodeSuggestions.number', - // name: 'Number of node suggestions', - // type: 'slider', - // defaultValue: 5, - // attrs: { min: 1, max: 100, step: 1 }, - // onChange: (v: number) => { maxSuggestions = v; applyDefaults() } - // }) - - const stored = _ext.setting.get('Comfy.NodeSuggestions.number') - if (stored !== undefined) maxSuggestions = stored - - // GAP-4: In v1, each node type's data was processed in `beforeRegisterNodeDef`. - // In v2 there is no equivalent; `processNodeDef` above exists as a named - // placeholder so Simon/Austin can see exactly what shape the hook needs. - // The actual invocation loop over all registered node types would go here - // once `onNodeTypeRegistered(def)` or an equivalent is added to the API. - void processNodeDef // referenced to avoid dead-code lint warning + // 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. } })