lifecycle.ts: clarify that defineNodeExtension, defineExtension, and
defineWidgetExtension are type-only exports for the npm package surface.
Runtime implementations are in extension-api-service.ts.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Prefix unused variable with _ (cleanups → _cleanups)
- Use globalThis.setInterval for portable timer type
- Prefix unused nodeType param with _
Note: Pre-existing TS errors in other files remain (lazy-serialize, bc-04, etc.)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Change widget.hidden from a plain property to a getter/setter that
proxies to options.hidden. This ensures Vue and canvas renderers
see the same visibility state.
Fixes the dual-hidden bug where Vue renderer reads options.hidden
and canvas renderer reads widget.hidden, causing visibility desync.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Remove unused _nextLinkId property from MockGraph interface
- Add test for out-of-bounds slot index edge case
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- knip.config.ts: drop `treatConfigHintsAsErrors` to false in tf only —
tf adds test consumers of foundation's @publicAPI-tagged symbols
(`_setDispatchImplForTesting`, `NodeExtensionOptions`, etc.) which
makes those tags 'redundant' from knip's POV. The tags are still
correct on foundation alone, so we keep the tag definition and just
downgrade hint→warning here.
- knip.config.ts: extend vitest config glob to include .mts (project is
"type": "module" so vitest configs use the .mts extension).
- bc-08/28/29/30 test files + ADR 0010: oxfmt formatting fixes after
rebase onto restacked ext.
Cascade follow-up to STACK-HYGIENE restratification. --no-verify used
because pre-commit runs full typecheck on staged TS files and tf has 61
pre-existing typecheck errors (same count with or without this commit);
per AGENTS.md rule #8, only lint/format/knip must pass during Phase A
on the ext-api stack.
Implement all 15 test stubs for BC.08 (programmatic linking) using synthetic
mock harnesses that verify the v2 API contract without requiring Phase B ECS:
bc-08.v2.test.ts (8 tests):
- NodeHandle.connect() creates links between output/input slots
- LinkHandle with stable id and isValid() tracking
- Replacing occupied input slot invalidates old LinkHandle
- TypeMismatchError thrown for incompatible slot types
- on('connectionChange') fires on both source and target handles
- disconnectInput() removes link and invalidates handle
- disconnectInput() on empty slot is no-op
bc-08.migration.test.ts (7 tests):
- v1/v2 connect() produce identical graph state
- Link IDs match between v1 and v2
- v2 throws TypeMismatchError where v1 returns null
- v1/v2 disconnectInput() leave identical state
- onConnectionsChange/connectionChange fire with equivalent payloads
- NodeHandle-based API vs raw node references
- Handle wraps same conceptual node as v1
This brings the test suite from 800 passed / 101 failed to 916 passed / 0 failed.
Note: --no-verify used because 207 pre-existing typecheck errors in other
test files (documented in RFR-12145) would fail the hook. Tests all pass.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove stub script that was pulled in from foundation rebase.
The tf branch has the real test script that runs vitest.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The CR-12142-3 fix moved DOM element storage from command options to a
side table for serializability. Update test to verify:
- Element NOT in command options (__domElement undefined)
- Element retrievable via getDOMWidgetElement(widgetId)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Delete truly unused variables and functions
- Prefix intentionally unused parameters with underscore
- Use void for side-effect-only expressions
Build now passes with vue-tsc --noEmit.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Fixes applied:
- defineNodeExtension → defineNode (all test files)
- widgetCreated → created (WidgetExtensionOptions hook name)
- Added apiVersion field to ExtensionOptions
- Fixed entity ID casts (number → as unknown as NodeEntityId/SlotEntityId)
- Harness barrel re-exports all types from harness/index.ts
Remaining errors (~61):
- Size type mismatch ([number,number] vs {width,height})
- WidgetValue/Handler type mismatches
- Unused variable warnings
- AppEvents constraint issues
- VirtualNode.isVirtualNode property access
See I-TF.9.K* tasks in todo.md for detailed breakdown.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Resolves remaining lint errors blocking lint-and-format CI on PR #12145:
- Removes unused vitest imports (afterEach, beforeEach, vi) flagged by
unused-imports/no-unused-imports across ~12 bc-*.test.ts files
- Replaces let with const where reassignment never occurred (prefer-const)
- Applies oxfmt line-width reflow across bc-* test files
CI's lint-and-format step runs lint:fix then tries to auto-commit through
husky, which triggers the full-repo typecheck. By committing the auto-fixes
locally, 'Check for changes' returns false in CI, skipping the broken
auto-commit step.
--no-verify used because husky pre-commit runs the full-repo typecheck
which still has 192 pre-existing errors documented as out-of-scope for
this PR.
CI's pnpm lint:fix step exits non-zero on:
- bc-02.migration.test.ts: prefer-const on v1Handle (assigned once)
- bc-35.v1.test.ts: typescript-eslint/no-unsafe-function-type on the
Function cast — replace with an explicit signature
Both errors block the [automated] auto-format push back to the PR.
oxlint's import/no-duplicates rule errored on bc-12, bc-31, bc-32
(both .v2 and .migration variants) because describe/it/expect and
expectTypeOf were imported from 'vitest' on separate lines. The CI
lint:fix step exits non-zero on these errors, blocking the
[automated] auto-format push back to the PR branch.
Targeted fixes for typecheck errors in 3 files. The remaining ~193
pre-existing errors live in other bc-* test files and are out of scope
for this branch (they will be addressed by sibling PRs in the stack).
- harness/runV2.ts: import WidgetExtensionOptions from
@/extension-api/lifecycle (the deprecated re-export shim in
@/types/extensionV2 doesn't include it).
- bc-37.migration.test.ts: prefix unused 'event' param with underscore;
widen 'eagerVueRef' literal-null type to VueComponentRef|null so the
optional-chain access type-checks; tighten 'intervalId' to const to
satisfy prefer-const lint that flagged after the file was touched.
- bc-36.v2.test.ts: drop unused 'beforeEach' import; drop unused
InputTextOptions interface; convert SelectOptions/SliderOptions to
type aliases that intersect Record<string, unknown> so the
'as unknown as ...Options' casts are assignable to setOptions().
Net: 10 typecheck errors removed (203 -> 193). Test intent preserved.
Pre-commit hook bypassed (--no-verify) because it runs full-repo
typecheck, which fails on pre-existing errors in non-scoped files.
Internal-only types were exported but never consumed outside their
source modules. Tighten the surface to satisfy `pnpm exec knip` so
the pre-push hook stays green:
- worldMocks: drop unused createWorldMockHandles helper (vi.hoisted
block is inlined per-consumer); demote WorldMockHandles to internal.
- v1App: demote V1NodeLike / V1Extension / V1App to internal types.
- v2Runtime: demote NodeRecord / V2Runtime / V2RuntimeOptions to
internal types.
F2: ~96 BC test files inline near-identical `createTestRuntime` /
`createV2Runtime` blocks (and ~30 `*.migration.test.ts` files inline
`createV1App`). Land the shared harness with bc-01 as the pilot:
- harness/v2Runtime.ts — canonical createV2Runtime({ idPrefix }) with
register / addNode / mountNode(id) / clear surface.
- harness/v1App.ts — canonical createV1App with simulateNodeCreated.
- harness/README.md — incremental migration tracker (which files have
adopted, which surfaces remain) and conventions for hoist-safe
vi.mock / vi.hoisted use against this harness.
bc-01.v2 keeps a thin local createTestRuntime alias to minimise churn
inside the test bodies; bc-01.migration bridges the legacy
`mountNode(comfyClass)` shape to the canonical `addNode + mountNode(id)`
without rewriting every assertion.
Verified: bc-01.v2 (13/14) + bc-01.migration (6/7) pass under vitest run
(1 it.todo each, pre-existing).
F3: bc-05.v2, bc-05.migration, bc-11.v2, bc-11.migration each duplicated
the same vi.mock('@/world/...') block (worldInstance + widgetComponents +
entityIds + componentKey + 3 extension-api stubs). Centralise the factory
bodies in src/extension-api-v2/__tests__/harness/worldMocks.ts and have
the four files consume them.
vi.hoisted handles stay inline because the hoisted factory runs before
imports resolve; vi.mock factories wrap the imported helpers in arrows
so the import binding is read lazily.
Verified: 4/4 files pass under vitest run with the shared module.
F5: D9 places several deferred behaviors in Phase C (canvas/menu
extension points), not strictly Phase B. Single-pass cosmetic relabel
to keep todos honest about which phase unblocks them.
F6: harness World was exposed without provenance, encouraging tests to
reach past the v1 `app` shape. Mark with @internal JSDoc so the
escape-hatch is documented and discouraged from public use. Existing
test usages (bc-15.*) continue to compile.
Restratified i-tf. Adds:
- src/extension-api-v2/__tests__/bc-XX.{v1,v2,migration}.test.ts triplets
for 41 behavior categories (BC.01-41) — real test bodies, not stubs
- src/extension-api-v2/harness/{comfyApp,index,loadEvidenceSnippet,runV1,
runV2,world}.ts and __fixtures__/touch-point-database.json
- vitest.extension-api.config.mts (test runner config)
- package.json — adds test:extension-api{,:watch,:coverage} scripts
Original (pre-restratify) branch tip backed up at
refs/backup/restratify-20260511/ext-api-i-tf.
Per workspace executive decision (option a) on F-12144-1: v1 + v2 conversions
in dynamicPrompts/imageCrop/previewAny coexist as Phase A demos following the
strangler-fig migration pattern (D6). Both register, but only one path runs
per node — guards skip v2 when v1 is already registered to prevent:
- dynamicPrompts: double processDynamicPrompt() on serialize
- previewAny: duplicate preview_markdown/preview_text/previewMode widgets
- imageCrop: redundant setSize call (idempotent but cleaner)
Guard pattern: 1-line useExtensionStore().isExtensionInstalled('<v1-name>') check
at the top of nodeCreated.
See workspace AGENTS.md rule #8 — CI failures on the ext-api stack do not
block flips during Phase A; full CI green required only at rebase point onto
PR #11939.
Cascades the foundation fix (8564a19dc7) into the published API
snapshot. NodeMode JSDoc now correctly maps numeric slots to
LGraphEventMode names (was wrong for slots 1-4 in prior snapshot).
Other diff churn is whitespace/format-only from a fresh dts build.
Adds `packages/extension-api/api-snapshot/` containing the generated
`.d.ts` files for every module re-exported from
`@comfyorg/extension-api`:
index.d.ts — barrel / entry point
events.d.ts — event payload types
identifiers.d.ts — branded entity ID types
lifecycle.d.ts — defineExtension / defineNodeExtension / defineWidgetExtension
node.d.ts — NodeHandle and DOM widget options
shell.d.ts — sidebar, bottom panel, command, toast types
types.d.ts — extension option contracts
widget.d.ts — WidgetHandle
`build/` stays gitignored. Snapshot is a separate stable path so
reviewers see exactly what extension authors will consume on a public
API change, without polluting git with runtime `.js` / per-module
internal declaration files. Regenerate via:
pnpm --filter @comfyorg/extension-api build
cp packages/extension-api/build/extension-api/*.d.ts \
packages/extension-api/api-snapshot/
eslint.config.ts: ignore `api-snapshot/**` so the generated declarations
do not need to live in any tsconfig project.
See `packages/extension-api/api-snapshot/README.md` for the contract.
Per D-no-node-widget-access (2026-05-19, bilateral A1 closure), nodes
can no longer enumerate widgets — `node.getWidgets()` was removed
from NodeHandle. The previous dynamicPrompts.v2 implementation
iterated node widgets to attach per-widget `beforeSerialize`
handlers; that pattern is now forbidden.
Stubbed pending a follow-up public API (`defineWidgetAugmenter` or
per-widget `setup` on `defineWidget`) that lets extensions attach
behavior to existing widget types matching a predicate. v1
(`Comfy.DynamicPrompts`) continues to work — this is a v2 surface
gap only, not a user-visible regression.
Refs: decisions/D-no-node-widget-access.md
Strangler-pattern v2 example demonstrating the single-coordinate-space policy from D-coord-space (ACCEPTED 2026-05-18, option iii) and Axiom A13 (Single Coordinate Space — Canvas).
Three sections: (1) default path — every NodeHandle spatial accessor speaks canvas units; (2) escape-hatch path — globalThis.app.canvas.{ds,canvas} + window.devicePixelRatio with the required '// escape-hatch — see D-coord-space.md' annotation comment on every use site; (3) cliff documentation — what's NOT on the public surface (no getScreenPosition, no space param, no branded ClientPoint type).
Serves as the executable canary that the documented pattern actually works. Authors and AI agents reading the v2 extensions/core/*.v2.ts directory see the policy demonstrated alongside the existing dynamicPrompts/previewAny/imageCrop/noteNode/slotDefaults/rerouteNode/webcamCapture examples.
knip.config.ts: add new file to strangler ignore list. Phase A gates: format clean / lint 0 errors 3 pre-existing warnings / knip 6 pre-existing tag hints + 1 pre-existing config hint.
Strangler-pattern port of the WEBCAM custom widget type from the v1 webcamCapture extension to the v2 mount-lifecycle seam (Axiom A12 / D-widget-converge §Decision).
- Registers WEBCAM via defineWidget({type:'WEBCAM',mount}). The mount body constructs the <video> + container, captures them via closure (no widget.element accessor exposed per A12), and returns a cleanup that stops MediaStream tracks on widget destruction. - Uses ctx.onAfterRemount to re-attach the cached container when the host is swapped (graph ↔ app mode swap, subgraph promotion) per D-widget-converge §Clarification 1 — mount body is NOT re-invoked. - Companion defineNode stays a placeholder: GAP-2 (no type-construction addWidget('button',…)) and GAP-11 (no async setSerializedValue path; v2's WidgetBeforeSerializeEvent doesn't yet promise async resolution) block the full node-side port. v1 webcamCapture.ts remains authoritative until those gaps close.
knip.config.ts: add the new file to ignore list (matches existing v2 strangler entries; not wired into bootstrap).
Phase A gates: lint 0 errors / 3 pre-existing warnings; format:check clean; knip 6 pre-existing tag hints + 1 pre-existing config hint / 0 new failures.
F-12144-2: rewrite shell.ts header to say 're-exported from' (matches the
re-export code; the original 'moved from' wording was ambiguous).
F-12144-3: tag SlotDefaults v2 extension as '(DEMO — incomplete migration)'
and add a JSDoc @remarks block listing the v1 features (beforeRegisterNodeDef
node metadata, settings-dialog contribution, slot-type registry mutation)
that remain TBD. Intentionally NOT porting them — staging-demo intent per the
GAP-4/5/6 feedback to Simon/Austin.
F-12144-4: drop the redundant 'as string' cast in dynamicPrompts.v2; the
immediate 'typeof value === "string"' check makes the cast self-contradicting.
Defensive runtime check stays.
F-12144-10: add a 3-line @example to defineWidgetExtension JSDoc to match the
defineNodeExtension/defineExtension docs.
F-12144-11: reorder DOMWidgetOptions and NodeHandle JSDoc blocks so each
docblock immediately precedes its declaration.
Per D9 strangler-bridge taxonomy, every direct globalThis.app access in
v2 conversion files must carry a 'strangler-bridge:Phase-B' marker so
the bridge audit can enumerate remaining touch-points before Phase-B
removes the global.
Tag the three sites in rerouteNode.v2.ts (configuringGraph guard in
onConnectionsChange, configuringGraph guard in updateLink, and the
canvas.setDirty call from the context-menu callback).
Known-stubbed channels (executed/connected/disconnected/configured)
dispatch to a Phase-A stub bus that does not deliver events. Previously
these registrations were silent — extensions saw no warning that their
handlers were dead until #11939 lands the dispatch substrate.
Add a DEV console.warn (eslint-disable-next-line no-console, matching
the established pattern in this file) for each known-stubbed channel
registration so authors know to wait on #11939.
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.
Hand-patched mirror of foundation hygiene cleanup per AGENTS.md Rule 8
(Node 20 vs vite blocks `pnpm build` in this worktree):
- Strip HR-style section dividers
- Strip parenthetical decision archaeology
((per D5), (D-immutability-enforcement, Hybrid C),
D-bootstrap-hooks (W6.P6.C), D-shell-ui-entrypoints (W6.P5.C), etc.)
from per-export JSDoc — preserve AXIOMS.md §A1/A14/A15/A16/A12 refs
and PHASE_A_EXCLUDED axiom blocks (functional content).
- Strip "W6.P8.UNMIGRATABLE / D-input-output-shape" todo refs.
- One stale `defineWidgetExtension` JSDoc example fixed (was already
on foundation; rebase carried the fix through).
Mirrors foundation commit `ee0537fdb5`.
ADR: decisions/D-design-review-hygiene-cleanup.md
Zero published-surface change (no types added/removed/renamed).
Hand-patched packages/extension-api/build/index.d.ts to match the
foundation surface after A16 closure:
- Drop `isSerializeEnabled` / `setSerializeEnabled` from WidgetHandle
- Drop `WidgetBeforeSerializeEvent.context` 4-way discriminator
- Drop `WidgetBeforeSerializeEvent.skip()`
- Drop `on('propertyChange', handler)` overload
- Drop entire `WidgetPropertyChangeEvent` interface
- Drop `WidgetOptions.serialize?: boolean` key
- Update JSDoc on `WidgetBeforeSerializeEvent` / `options` / `serializeValue`
to reference A16 / drop transport-discriminator examples
`NodeBeforeSerializeEvent.context` is intentionally retained — the
node-level surface stays `@deprecated` + runtime-warned per ADR-0010
(not banned by D-widget-serialization-simplification).
Hand-edit pattern because pnpm build is blocked by Node 20 vs vite
requirement per AGENTS.md Rule 8. Surface verified to match foundation
HEAD e56187adf3.
Refs: decisions/D-widget-serialization-simplification.md, AXIOMS.md §A15+A16