Skip to content

[Phase A · Base] ECS + Vue hoisted client state & hook API — research bundle + canonical surface#12101

Draft
christian-byrne wants to merge 5 commits intomainfrom
ecs-vue-hoisted-client-state-hook-api
Draft

[Phase A · Base] ECS + Vue hoisted client state & hook API — research bundle + canonical surface#12101
christian-byrne wants to merge 5 commits intomainfrom
ecs-vue-hoisted-client-state-hook-api

Conversation

@christian-byrne
Copy link
Copy Markdown
Contributor

@christian-byrne christian-byrne commented May 9, 2026

Phase A integration branch for the new extension API (@comfyorg/extension-api).

This PR is the base of a 4-PR draft stack. Each child is a separate review surface for narrow scope.

Stack

PR Branch Stacks on What is in
#12101 (this) ecs-vue-hoisted-client-state-hook-api main Research bundle + first-pass surface shim + early test stubs
#12102 ext-api/i-foundation this Decl polish + service rename + scope registry + ECS world (3 commits)
#12103 ext-api/i-pkg this @comfyorg/extension-api npm package + TypeDoc → MDX docgen + CI workflows (sidecar)
#12104 ext-api/i-tf i-foundation Test framework reorg + harness + content fill (I-TF.2/3/6)
#12105 ext-api/i-ext i-foundation Three new core extension conversions (noteNode/rerouteNode/slotDefaults)

What is in this base PR

Coworker fork point

Coworkers converting core extensions should branch off ext-api/i-foundation (#12102) in parallel to #12105.

Refs

  • ADR 0008 (ECS architecture)
  • D5 events, D6 parallel paths, D7 widget shape, D8 ECS adapter, D9 strangler phases, D10 lifecycle, D12 scope clone-on-copy
  • R4 custom-node patterns, R7 touch-point DB, R8 clone-and-grep
  • 41 behavior categories × 3 stub types covered by I-TF compat-floor

Draft for review collaboration via inline comments.

┆Issue is synchronized with this Notion page by Unito

Connor Byrne and others added 5 commits May 7, 2026 17:45
Compatibility-surface map for the upcoming v2 extension API redesign.
52 patterns across 15 surface families derived from:
- AGENTS.md §5 (40+ repo callouts)
- ADR 0008 entity taxonomy
- widget-api-thoughts.md output system axis
- Real-world MCP code-search across 87 ecosystem repos

See docs/architecture/extension-api-v2/README.md.
Adds 21 evidence rows + 4 new patterns from second MCP code-search burst:

  S11.G3 graph.beforeChange/afterChange — explicit batching seam
  S7.G1  window.LiteGraph / window.comfyAPI globals (CRITICAL)
  S11.G4 graph.setDirtyCanvas — manual redraw flush idiom
  S10.D3 node.setSize(node.computeSize()) — manual relayout idiom

Pattern S11.G4 + S10.D3 together justify v2's reactive-layout value
proposition; S11.G3 motivates a first-class world.batch() API; S7.G1
sets the deprecation sequence for window globals.

star-cache.yaml grew 87 → 105 starred repos; #1 blast-radius is still
S6.A1 graphToPrompt (★17,122) — unchanged.

Reproducible via docs/architecture/extension-api-v2/scripts/add-evidence-pass2.py
Adds the v2 extension API public surface:

  src/types/extensionV2.ts          (169 lines) — branded entity IDs,
    geometry primitives, slot/widget options, NodeHandle/WidgetHandle
    contracts. Pure types. The stable public surface extensions depend on.

  src/services/extensionV2Service.ts (413 lines) — surface-only impl.
    Thin pass-throughs to existing LGraphNode/widget/canvas. No new
    internal architecture; no patching guards yet (Phase A scope per D9).
    Reactive mounting via Vue EffectScope; scope registry keyed by
    extension:entityId.

  src/extensions/core/{dynamicPrompts,imageCrop,previewAny}.v2.ts
    Three smallest core extensions ported as proof-of-concept. Used
    as the I-COORD.1 reference for Simon + Austin to push back on
    the API shape with concrete code.

Phase A goals (per todo.md "Branch + phasing topology"):
  - Stable v2 surface coworkers can branch off
  - Internal methods are pass-throughs (no behavior change)
  - I-PG.A: no interception/blocking; v1 path coexists unchanged

Stacks under this branch (will be opened as draft PRs):
  - ext-v2/i-tf-test-framework
  - ext-v2/i-sr-scope-registry
  - ext-v2/i-ws-lazy-serialize
Merges three pass-3 staging sources via scripts/merge-staging-pass3.py:

  R8 (clone-grep top-20 ecosystem packs)  — 887 raw rows, 380 deduped
  R9.security                             — 3 NEW S16.* dynamic-code patterns
  R9.cookiecutter / R9.guides             — sanctioned-public surface evidence

Rollup re-ranked. Notable shifts:

  rank   pattern                                              ★sum  occ
   1     S6.A1  graphToPrompt monkey-patching ⚠️ CRITICAL    23,604  15
   2     S2.N15 prototype.serialize replacement                5,996  12
   3     S11.G2 graph add/remove/findNodesByType              21,086  12
   4     S11.G3 beforeChange/afterChange batching             18,973  13
   5     S7.G1  window.LiteGraph globals as ABI               18,853  14
   6     S6.A4  app.queuePrompt patching                       3,464  11
   7     S10.D1 dynamic addInput/Output                        5,287  11
   8     S10.D2 node.connect/disconnect programmatic          18,643  14
   9     S4.W4  widget.options.values mutation                 7,985  11
  10     S2.N16 node.widgets array access                      7,086  13

S11.G3 (beforeChange batching) and S7.G1 (window globals) entered top-5 —
both drive the v2 'three orthogonal capabilities' thesis: batched mutations,
typed import deprecation path.

3 NEW S16.* patterns from security scan:
  S16.D1 SVG with onload/onerror (XSS-equivalent dynamic JS)
  S16.D3 obfuscated string-replace pipeline
  S16.D7 Python web/ template uses exec/eval to emit JS

star-cache: 105 → 119 starred repos (added Acly, ssitu, cubiq, Fannovel16,
WASasquatch, Crystools, cg-use-everywhere, mikey_nodes, etc. — actual
top-20 ecosystem heavyweights now represented).
…-TF)

Adds the isolated vitest config, CI workflow, and all 41×3 compat-floor
stub triples so the blast_radius≥2.0 gate passes from day one.

- vitest.extension-api.config.mts — targets src/extension-api-v2/__tests__/
- .github/workflows/ci-tests-extension-api.yaml — two jobs: vitest run +
  python3 scripts/check-compat-floor.py (exits 1 on missing stubs)
- package.json — test:extension-api / :watch / :coverage scripts
- src/extension-api/ — public declaration files (NodeHandle, WidgetHandle,
  defineNodeExtension, typed events, lifecycle hooks)
- src/extension-api-v2/__tests__/v1|v2|migration — 123 stub files covering
  BC.01–BC.41 (all 34 compat-floor categories × 3 stub types)
- packages/extension-api/ — typedoc wrapper package

compat-floor gate: OK — 102 stub files present (34 categories × 3 types)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

📝 Walkthrough

Walkthrough

This change establishes a complete V2 Extension API framework for ComfyUI: a stable, branded TypeScript API contract, canonical YAML compatibility DB with evidence/rollup automation, triple-format (v1/v2/migration) BC test scaffolds, real node extension examples, documentation build/publish pipeline, CI/test coverage, and service/mount logic.

Changes

Extension API V2: Contracts, BC, Compatibility, Tooling, Docs

Layer / File(s) Summary
Core TypeScript API: Contracts, Branded Handles, Events, Lifecycle, IDs
src/extension-api/**/*, src/types/extensionV2.ts
Stable, fully-documented exported API contracts for node/widget/shell/event/entity identifier/lifecycle, rules and documentation for authoring naming/exports, and branded ID and event typing.
Compatibility YAML DB, Blast-radius Rollup, Star Cache, Evidence Tooling
docs/architecture/extension-api-v2/, docs/architecture/extension-api-v2/scripts/*
YAML touch-point/compatibility database, blast-radius scoring formula, star cache, migration plan, evidence ingestion and rollup/merge scripts, and all doc/plan/spec outputs for BC evaluation and evidence tracking.
Live V2 Extension Example Modules
src/extensions/core/dynamicPrompts.v2.ts, src/extensions/core/imageCrop.v2.ts, src/extensions/core/previewAny.v2.ts
New node extension modules using only the public API: dynamic prompt transform (custom serialization), min-size enforcement (image crop), and preview widgets (output/text population).
V2 Extension Service, Handles, ECS Mounting System
src/services/extensionV2Service.ts
Service layer for per-entity ECS-based handle binding, extension mounting (per node/widget), Vue EffectScope management, registration logic, runtime handle APIs, and registration APIs which back the public TS contract.
Backward-compatibility Contract/Migration Test Suites
src/extension-api-v2/__tests__/**/*
Scaffolded/test-organized Vitest scenarios for every v1/v2/migration scenario, in BC triple format, referencing compatibility DB patterns/floor, covering node, widget, graph, shell UI, event, and surface contract points.
Documentation Build, Typedoc/Mintlify Pipeline, Package Scaffold
packages/extension-api/**/*, src/extension-api/README.md
Adds all doc build tooling (Typedoc, Mintlify), postprocessing, nav, .gitignore, versioned README with authoring/versioning policy, scaffolding package.json, tsconfig.docs, and documentation pipeline glue.
CI Workflows, Coverage, Automated Compat-Floor Enforcement
.github/workflows/ci-tests-extension-api.yaml, package.json, vitest.extension-api.config.mts
Adds a dedicated CI job for extension API BC suite with test/coverage, coverage upload, compat-floor gating driven by blast-radius/stub presence, and a focused Vitest config and npm scripts for reproducible checks.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Poem

In a warren of code so neat,
Rabbits hop where contracts meet.
Each node, event, and widget shines,
Through tests, docs, and BC lines.
The blast of stars has scored the floor—
Extensions flourish evermore!
🐇✨ Hop on, let the plugins soar!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ecs-vue-hoisted-client-state-hook-api

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 9, 2026

🌐 Website E2E

Caution

Some tests failed.

Status ❌ Failed
Report unavailable
Screenshot diffs 0
Other failures 0

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 9, 2026

🎭 Playwright: 🕵🏻 0 passed, 0 failed

📊 Browser Reports
  • chromium: ❌ Deployment failed
  • chromium-2x: ❌ Deployment failed
  • chromium-0.5x: ❌ Deployment failed
  • mobile-chrome: ❌ Deployment failed

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 9, 2026

🎨 Storybook: ❌ Failed

Details

⏰ Completed at: 05/09/2026, 04:42:18 AM UTC

Links

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 9, 2026

📦 Bundle Size

⚠️ Size data collection failed. Check the CI workflow logs.

⚡ Performance

⚠️ Performance tests failed. Check the CI workflow logs.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

🟠 Major comments (36)
src/extension-api-v2/__tests__/v1/BC.08.v1.test.ts-2-8 (1)

2-8: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

compat-floor: yes conflicts with TODO-only coverage.

Line 2 marks this suite as compat-floor enforced, but Lines 6–8 are non-executable TODOs, so no actual floor is enforced yet. Either implement at least one runnable assertion now or temporarily mark this suite compat-floor: no until executable cases land.

Quick metadata-safe interim fix
-// blast_radius: 11.81  compat-floor: yes
+// blast_radius: 11.81  compat-floor: no

As per coding guidelines "Write tests for all changes, especially bug fixes to catch future regressions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/v1/BC.08.v1.test.ts` around lines 2 - 8, The
suite is marked compat-floor: yes but contains only it.todo entries, so no
executable assertions satisfy the floor; either change the suite metadata from
"compat-floor: yes" to "compat-floor: no" or add at least one runnable test (for
example implement an it() that uses app.graph.connectNodes(srcId, srcSlot,
dstId, dstSlot) and asserts graph.links or onConnectionsChange) inside the
describe('BC.08 v1 — Programmatic linking') block to provide executable coverage
and satisfy the compat-floor requirement.
src/extension-api-v2/__tests__/v1/BC.09.v1.test.ts-2-9 (1)

2-9: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Declared compat-floor is not currently enforced by this suite.

Line 2 marks BC.09 as compat-floor, but Lines 6–9 are all TODO placeholders. Please add at least one executable parity test now, or set compat-floor to no until runnable cases are added.

Quick metadata-safe interim fix
-// blast_radius: 38.63  compat-floor: yes
+// blast_radius: 38.63  compat-floor: no

As per coding guidelines "Write tests for all changes, especially bug fixes to catch future regressions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/v1/BC.09.v1.test.ts` around lines 2 - 9, The
test suite declares compat-floor but contains only TODOs; either add at least
one runnable parity test inside the describe block "BC.09 v1 — Dynamic slot and
output mutation" (replace one of the it.todo entries such as the
node.addInput/node.addOutput/node.removeInput/dynamically added outputs case
with an actual it(...) test that sets up a node, performs the dynamic
slot/output mutation via the public API, asserts the canvas/connectivity or
serialized node.outputs, and cleans up), or change the compat-floor marker from
yes to no so the suite is not treated as a compat-floor; update the describe
block by replacing one it.todo with a concrete assertion-driven test (use
existing test helpers in the repo) or flip the compat-floor flag to "no".
src/extension-api-v2/__tests__/v2/BC.23.v2.test.ts-2-9 (1)

2-9: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Compat-floor is declared, but no executable tests are present.

With compat-floor: yes on Line 2, this suite should enforce behavior now; TODO-only cases on Lines 6–9 do not. Please either add at least one concrete assertion or downgrade compat-floor until implementation is ready.

Quick metadata-safe interim fix
-// blast_radius: 14.42  compat-floor: yes
+// blast_radius: 14.42  compat-floor: no

As per coding guidelines "Write tests for all changes, especially bug fixes to catch future regressions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/v2/BC.23.v2.test.ts` around lines 2 - 9, The
test suite currently declares "compat-floor: yes" but contains only it.todo
entries, so add at least one executable test assertion or remove/downgrade the
compat-floor marker; locate the describe('BC.23 v2 — Node property bag
mutations') block and replace or supplement one of the it.todo('...') entries
with a concrete it(...) that asserts expected behavior (e.g., call the
NodeHandle API such as setTitle/getTitle or event hooks like on('configured')
and assert results) or if you can't implement a test yet, change/remove the
compat-floor comment so the suite doesn't require executable tests.
src/extension-api-v2/__tests__/v2/BC.15.v2.test.ts-5-9 (1)

5-9: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Compat-floor scenario has no executable assertions yet

Lines 6-8 are all it.todo, so this “compat-floor: yes” case currently can’t catch regressions in workflow-load hook behavior. Please convert at least one scenario to a runnable assertion in this PR (even a minimal smoke path), then keep the rest as TODOs.

As per coding guidelines "Write tests for all changes, especially bug fixes to catch future regressions".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/v2/BC.15.v2.test.ts` around lines 5 - 9, The
test file declares a describe block 'BC.15 v2 — Workflow loading into the
editor' with three it.todo entries; convert at least one of them (e.g., the
first: "defineExtension setup can register a loadWorkflow hook that fires when a
workflow JSON is loaded") into a runnable assertion by replacing the it.todo
with an it test that mounts or initializes the v2 extension environment,
registers a minimal defineExtension providing a loadWorkflow hook (reference the
hook name loadWorkflow and the test case BC.15 v2), triggers the code path that
loads a simple workflow JSON, and asserts the hook was called (e.g., by using a
spy/flag or mock) so the test exercises the hook firing; keep the other two
it.todo entries unchanged.
src/extension-api-v2/__tests__/bc-14.migration.test.ts-10-39 (1)

10-39: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Compat-floor migration suite is non-executable (it.todo only).

This currently provides zero behavioral verification for a high-blast-radius contract. Please convert core parity scenarios to runnable assertions (or keep it out of compat-floor accounting until executable).

As per coding guidelines: "Write tests for all changes, especially bug fixes to catch future regressions".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/bc-14.migration.test.ts` around lines 10 - 39,
The suite "BC.14 migration — graphToPrompt interception" currently contains only
it.todo placeholders; replace the core parity todos with real tests that assert
functional equivalence or remove them from compat-floor: for each todo under the
"payload equivalence", "execution ordering", and "coexistence during migration
window" describes, implement an it(...) that sets up a minimal reproducible
scenario (e.g., create a sample {output, workflow} payload, register a v1
monkey-patch and the v2 beforeGraphToPrompt handler, or simulate cancellation),
execute both codepaths (invoke the v1 wrapper and the v2 handler pipeline using
the same inputs), and use expect(...) assertions to compare final payloads,
injected metadata, virtual-node removal behavior, ordering effects, and
cancellation side-effects; alternatively, if coverage cannot be implemented now,
replace the it.todo with it.skip or remove the test from compat-floor
accounting. Ensure tests reference the suite name and the specific behaviors
(payload equivalence, execution ordering, coexistence) so reviewers can locate
them.
src/extension-api-v2/__tests__/migration/BC.37.migration.test.ts-1-9 (1)

1-9: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Remove duplicate BC contract test files across all 41 migration contracts.

The test suite has systematic duplication: all 41 BC contracts exist in both naming conventions (bc-NN.type.test.ts at root and BC.NN.type.test.ts in subdirectories). The Vitest glob pattern src/extension-api-v2/__tests__/**/*.{test,spec}.{ts,mts} discovers both sets, risking double execution and ambiguous compat-floor accounting.

Keep one canonical structure (recommend the subdirectory approach with uppercase naming for clarity), and remove all 123 duplicate files from the root __tests__/ directory. This affects all three variants (migration, v1, v2) for each of the 41 contracts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/migration/BC.37.migration.test.ts` around
lines 1 - 9, The test suite duplicates 41 BC contract tests causing double
execution; keep the canonical uppercase subdirectory versions (e.g., the
describe titled "BC.37 migration — VueNode bridge timing — deferred mount
access") and delete the duplicate root copies (the bc-NN.type.test.ts variants)
across all three variants (migration, v1, v2); then update the Vitest glob (the
pattern src/extension-api-v2/__tests__/**/*.{test,spec}.{ts,mts}) to only match
the retained subdirectory structure so only the BC.NN.*.test files run and
remove the 123 root duplicate files.
src/extension-api-v2/__tests__/v1/BC.37.v1.test.ts-6-8 (1)

6-8: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

it.todo placeholders provide no compatibility protection yet

These cases currently don’t execute or assert behavior, so this contract file won’t catch regressions in BC.37 until at least core scenarios are converted to runnable tests.

As per coding guidelines, “Write tests for all changes, especially bug fixes to catch future regressions”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/v1/BC.37.v1.test.ts` around lines 6 - 8,
Replace the three it.todo placeholders in the BC.37.v1.test.ts file with
runnable tests: convert the first todo into an it test that mounts a
VueNode-backed widget and asserts widget.element is null or undefined when
accessed synchronously inside nodeCreated; convert the second into an it that
awaits nextTick (imported from 'vue' or test utils) after nodeCreated and
asserts widget.element is non-null; and convert the third into an it that defers
DOM access with Promise.resolve() inside nodeCreated and asserts the resolved
tick sees a fully initialized VueNode element—locate the tests by the todo
descriptions and ensure you use the same widget creation/mount helpers used
elsewhere in the suite and include proper assertions instead of todo
placeholders.
src/extension-api-v2/__tests__/bc-31.v1.test.ts-40-46 (1)

40-46: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add trusted-source and failure-path expectations for external asset loading

These TODOs currently validate only happy-path injection. The migration contract should also require trusted-source validation and explicit error propagation for rejected/failed loads.

Suggested TODO additions
   describe('S16.DOM4 — external script/asset loading via DOM', () => {
     it.todo(
       'extension can dynamically create and append a <script> element to load external code'
     )
     it.todo(
       'extension can create a <link rel="stylesheet"> element for external CSS'
     )
+    it.todo(
+      'external asset URLs are validated against trusted sources before insertion'
+    )
+    it.todo(
+      'failed or blocked asset loads propagate actionable errors to the caller'
+    )
   })

As per coding guidelines, “Validate trusted sources before processing” and “Implement proper error propagation”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/bc-31.v1.test.ts` around lines 40 - 46, The
two it.todo tests under the 'S16.DOM4 — external script/asset loading via DOM'
suite currently only cover happy-path injection; replace each TODO with concrete
tests that (1) assert trusted-source validation is enforced by simulating an
extension attempt to create/append a <script> or <link> with a disallowed URL
and expecting the operation to be rejected/throw (covering the "trusted-source"
requirement) and (2) assert explicit error propagation by simulating a load
failure (triggering the element's error event) and verifying that the code path
handling dynamic insertion surfaces/propagates that error to the caller. Target
the existing test identifiers (the two it.todo entries) and use the DOM element
creation/append sequence plus onload/onerror handling to validate both
trust-check and failure-path behavior. Ensure tests assert both allowed sources
succeed and disallowed sources or network/load errors produce explicit
rejections/errors.
src/extension-api/identifiers.ts-13-25 (1)

13-25: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Convert all @/ aliases to relative paths in extension-api sources to prevent alias leakage in published declarations.

The @comfyorg/extension-api package is built with tsc --emitDeclarationOnly, which will emit .d.ts files to npm. Since packages/extension-api has no tsconfig.json, it inherits the root configuration with "@/*": ["./src/*"]. Downstream npm consumers won't have this alias configured, breaking their TypeScript resolution.

Five @/ imports across four files need fixing:

  • src/extension-api/identifiers.ts (lines 16, 25): from '@/types/nodeIdentification'
  • src/extension-api/shell.ts (line 21): from '@/types/extensionTypes'
  • src/extension-api/lifecycle.ts (line 306): from '@/services/extension-api-service'
  • src/extension-api/index.ts (line 64): from '@/services/extension-api-service'

Convert each to relative paths (e.g., from '../types/nodeIdentification').

Example for identifiers.ts
 export type {
   NodeLocatorId,
   NodeExecutionId
-} from '@/types/nodeIdentification'
+} from '../types/nodeIdentification'

 export {
   isNodeLocatorId,
   isNodeExecutionId,
   parseNodeLocatorId,
   createNodeLocatorId,
   parseNodeExecutionId,
   createNodeExecutionId
-} from '@/types/nodeIdentification'
+} from '../types/nodeIdentification'
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api/identifiers.ts` around lines 13 - 25, Replace the aliased
import path '@/types/nodeIdentification' with a relative path so generated .d.ts
files don't reference the project alias: update both exports in this file that
re-export NodeLocatorId, NodeExecutionId and the functions isNodeLocatorId,
isNodeExecutionId, parseNodeLocatorId, createNodeLocatorId,
parseNodeExecutionId, createNodeExecutionId to import from the relative module
(e.g., '../types/nodeIdentification') instead of '@/types/nodeIdentification'.
src/extension-api-v2/__tests__/bc-31.v2.test.ts-9-55 (1)

9-55: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace TODO-only contract cases with at least one executable check

This suite is entirely it.todo(...), so CI can pass without validating BC.31 behavior. Please add at least one runnable assertion for the highest-risk path (e.g., sanitization or style cleanup) in this file.

As per coding guidelines: "Write tests for all changes, especially bug fixes to catch future regressions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/bc-31.v2.test.ts` around lines 9 - 55, Replace
at least one it.todo with a real test that exercises the highest-risk path: call
extensionManager.injectStyles(css) (or the extension.addPanel/addToolbarItem
variants if easier) with a unique CSS string, assert a <style> tag with that
content is appended to document.head, then call the returned cleanup handle or
unregister the extension scope and assert the style tag is removed; reference
injectStyles(css), extensionManager.injectStyles, and the cleanup/unregister
handle to locate where to add the assertions. Ensure the test runs in Jest (not
todo) and cleans up DOM state so it won’t leak into other tests.
src/extension-api-v2/__tests__/bc-06.v1.test.ts-14-54 (1)

14-54: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add one executable BC.06 guard test for canvas override behavior

The suite is currently all TODOs, so prototype-override regressions won’t be detected. Please add at least one runnable assertion for a high-risk canvas override path.

As per coding guidelines: "Write tests for all changes, especially bug fixes to catch future regressions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/bc-06.v1.test.ts` around lines 14 - 54, Add a
runnable test that asserts prototype override behavior instead of leaving todos:
implement a test in this file that temporarily replaces
LGraphCanvas.prototype.drawNodeShape (or
LGraphCanvas.prototype.processContextMenu) with a spy/flagging function, creates
an LGraphCanvas instance, triggers the relevant action (render or right-click),
and asserts the spy was invoked (and that calling the original is optional
depending on which behavior you check); make sure to restore the original
prototype method at test teardown so other tests are unaffected and reference
the exact symbols LGraphCanvas.prototype.drawNodeShape or
LGraphCanvas.prototype.processContextMenu to locate the code to modify.
src/extension-api-v2/__tests__/bc-21.migration.test.ts-11-33 (1)

11-33: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

BC.21 migration needs at least one executable contract assertion

All checks are TODO placeholders, so none of the migration guarantees are currently enforced. Please add one runnable assertion for widget registration/cleanup parity.

As per coding guidelines: "Write tests for all changes, especially bug fixes to catch future regressions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/bc-21.migration.test.ts` around lines 11 - 33,
The tests currently contain only it.todo placeholders; add at least one
executable test that asserts migration BC.21 behavior—e.g., replace one of the
todos under "scope cleanup on dispose" with a real test that registers a v2
widget via defineWidgetExtension(handle,...), verifies the widget appears in
getCustomWidgets (or node.widgets after nodeCreated), then calls the extension's
dispose/unregister and asserts the widget type is removed and nodes fall back to
default rendering; use the existing test helpers to create a temporary
extension, call defineWidgetExtension and nodeCreated, inspect node.widgets (or
getCustomWidgets), then call dispose and re-check that the custom widget type no
longer exists. Ensure the test names match existing descriptions (e.g., "v2
cleanup on dispose does not affect widget types registered by other extensions")
and exercise defineWidgetExtension, getCustomWidgets/nodeCreated, and dispose to
produce a runnable assertion rather than a TODO.
src/extension-api-v2/__tests__/bc-08.v2.test.ts-10-38 (1)

10-38: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add at least one executable BC.08 v2 assertion

All cases are it.todo(...), which gives no runtime signal for linking compatibility. Add one concrete it(...) assertion for connect/disconnectInput to prevent false-green CI on this contract.

As per coding guidelines: "Write tests for all changes, especially bug fixes to catch future regressions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/bc-08.v2.test.ts` around lines 10 - 38,
Replace at least one it.todo with an executable test that actually exercises
NodeHandle.connect(slotIndex, targetHandle, dstSlot) and
NodeHandle.disconnectInput(slotIndex): create two nodes via the test harness,
call sourceHandle.connect(outIndex, targetHandle, inIndex) and assert the
returned LinkHandle is valid and matches the graph (e.g., stable id present and
link exists), then call targetHandle.disconnectInput(inIndex) and assert the
LinkHandle becomes invalid and the graph no longer contains the link; also
assert on('connectionChange') handlers on both NodeHandles were invoked. Use the
existing test utilities to create nodes and obtain NodeHandle/LinkHandle
references; focus on exercising NodeHandle.connect, NodeHandle.disconnectInput,
LinkHandle validity and the 'connectionChange' event.
src/extension-api-v2/__tests__/bc-03.migration.test.ts-10-35 (1)

10-35: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Migration suite needs at least one runnable parity test

With only it.todo(...), this file does not currently validate BC.03 migration behavior. Please add one executable parity assertion (e.g., workflow-load onConfigure call semantics) to anchor the suite.

As per coding guidelines: "Write tests for all changes, especially bug fixes to catch future regressions."

src/extension-api-v2/__tests__/bc-19.v1.test.ts-11-30 (1)

11-30: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Queue interception contract should include a runnable test

This v1 contract suite is TODO-only, so it won’t catch regressions in wrapper behavior. Add at least one executable queuePrompt interception assertion in this file.

As per coding guidelines: "Write tests for all changes, especially bug fixes to catch future regressions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/bc-19.v1.test.ts` around lines 11 - 30,
Replace at least one of the todo tests under the "S6.A4 — queuePrompt
interception" suite with an executable test that registers an extension wrapper
around app.queuePrompt, captures a reference to the original (orig) function,
calls the wrapped app.queuePrompt with sample args (e.g., 0, 1), and asserts the
wrapper invoked orig with the same (number, batchCount) args and returned orig's
result; specifically implement a test that verifies the wrapper receives
(number, batchCount), calls orig(), and the returned value from app.queuePrompt
equals the orig() result so this file's queuePrompt interception behavior is
actually validated.
src/extension-api-v2/__tests__/v2/BC.02.v2.test.ts-6-8 (1)

6-8: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

BC.02 TODO wording conflicts with the current removed handler signature.

Line 7 implies a callback argument (NodeHandle payload), but src/extension-api/node.ts currently types on('removed', handler) as Handler<void>. Align the contract text and API before downstream PRs branch on this.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/v2/BC.02.v2.test.ts` around lines 6 - 8, The
TODOs assert that the removed event handler receives a NodeHandle payload but
the API currently types on('removed', handler) as Handler<void>, so update the
contract to be consistent: either change the API signature in
NodeHandle/on('removed', handler) from Handler<void> to Handler<NodeHandle> (and
update any references to onNodeRemoved) or reword the test TODOs to reflect no
payload (e.g., removed handler receives no arguments); locate the handler typing
in the NodeHandle/on('removed', handler) declaration and adjust the type and
related docs/tests accordingly.
src/extension-api-v2/__tests__/v2/BC.07.v2.test.ts-6-9 (1)

6-9: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

BC.07 contract TODOs currently describe APIs that are not in NodeHandle.

The TODOs mention slot-index payloads and veto/intercept callbacks, but the exported NodeHandle surface only defines passive connected/disconnected listeners with SlotInfo payloads and no veto hook. Please align either the API or these contract statements.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/v2/BC.07.v2.test.ts` around lines 6 - 9, The
test TODOs describe APIs that don't exist on NodeHandle: they expect slot-index
payloads and a connection-intercept/veto hook, while the current NodeHandle only
exposes passive connected/disconnected listeners with SlotInfo payloads; either
update the TODOs to match the existing API or add the missing surface to the
implementation—specifically, either change the four TODO descriptions in
BC.07.v2.test.ts to reference NodeHandle.on("connected", handler: (info:
SlotInfo) => void) and NodeHandle.on("disconnected", handler: (info: SlotInfo)
=> void), or extend the NodeHandle interface/implementation to include an
on("connected", handler: (slotIndex: number, nodeInfo: NodeInfo) => void),
on("disconnected", handler: (slotIndex: number) => void) and a connection
intercept hook (e.g., NodeHandle.setConnectionInterceptor or
NodeManager.onIncomingConnection that returns boolean to veto) so the tests and
API match.
src/extension-api-v2/__tests__/bc-10.v2.test.ts-10-33 (1)

10-33: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Resolve BC.10 contract drift before locking compat-floor semantics.

Line 12 defines on('change') as (newValue, oldValue), but Line 21 expects event.preventDefault().
Also, Lines 25-34 rely on NodeHandle.on('widgetChanged', ...), which is not present in src/extension-api/node.ts. Please align these TODO contracts (or the API) to a single canonical shape.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/bc-10.v2.test.ts` around lines 10 - 33, Tests
and comment reveal API mismatch: WidgetHandle.on('change') is implemented as
(newValue, oldValue) but tests expect an event with preventDefault(), and
NodeHandle.on('widgetChanged') is referenced but missing; pick a single
canonical shape and update code to match. Change WidgetHandle.on('change')
handler signature to receive an event object { newValue, oldValue,
preventDefault() } and update the emitter so per-widget listeners get that
event; implement NodeHandle.on('widgetChanged') in the node-level API (class
NodeHandle) to accept listeners and emit after all per-widget on('change')
calls, passing payload { name, value, oldValue, widget } as the event argument;
ensure preventDefault on the per-widget event can veto the write so the emitter
honors event.preventDefault().
src/extension-api-v2/__tests__/v2/BC.01.v2.test.ts-9-9 (1)

9-9: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the actual NodeHandle method names in the BC.01 contract text.

Line 9 references getInputs() / getOutputs(), but the exported API in src/extension-api/node.ts defines inputs() / outputs().

✏️ Suggested TODO text update
-  it.todo('NodeHandle.getInputs() and getOutputs() reflect the node slot configuration at creation')
+  it.todo('NodeHandle.inputs() and outputs() reflect the node slot configuration at creation')
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/v2/BC.01.v2.test.ts` at line 9, The BC.01 test
TODO references non-existent methods getInputs() and getOutputs(); update the
test text to use the actual exported NodeHandle API method names inputs() and
outputs() so the contract matches src/extension-api/node.ts—change the todo
string in BC.01.v2.test.ts to mention NodeHandle.inputs() and
NodeHandle.outputs().
docs/architecture/extension-api-v2/scripts/rollup-blast-radius.py-5-10 (1)

5-10: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

These weights drifted from the formula documented in touch-points-plan.md.

The plan defines 0.40 / 0.20 / 0.15 / 0.15 / 0.10, but this script bakes in 1.0 / 0.7 / 0.5 / 0.5 / 0.4. That materially changes ranking and the blast_radius >= 2.0 compat-floor threshold, so the script and docs need to be brought back into sync before this rollup is used as a gate.

Also applies to: 26-32

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/architecture/extension-api-v2/scripts/rollup-blast-radius.py` around
lines 5 - 10, The documented blast-radius weights in the header comment and in
the implementation (variables w_stars, w_occ, w_sig, w_silent, w_lifecycle) do
not match touch-points-plan.md; update the default weight values from
1.0/0.7/0.5/0.5/0.4 to 0.40/0.20/0.15/0.15/0.10 everywhere they are defined or
used (including the occurrences referenced around lines 26-32) and ensure the
compat-floor threshold (the blast_radius >= 2.0 check) is reviewed/adjusted if
the threshold in plan differs so the rollup logic and the header comment stay in
sync.
docs/architecture/extension-api-v2/README.md-50-69 (1)

50-69: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

This “Top 12” table is out of sync with touch-points-rollup.yaml.

For example, S6.A1 is 7.016 / 15 / 23604 in the checked-in rollup, not 6.67 / 7 / 17101 here. Because this README is presented as the canonical prioritization view, stale rankings will steer follow-up work toward the wrong surfaces.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/architecture/extension-api-v2/README.md` around lines 50 - 69, The Top
12 table in README.md is stale versus the authoritative
touch-points-rollup.yaml; regenerate the table by reading
touch-points-rollup.yaml, recomputing blast radius using the formula shown
(blast radius = log10(1+stars)*1.0 + log10(1+occurrences)*0.7 +
(signature_count-1)*0.5 + silent_breakage*0.5 + lifecycle_coupling*0.4), sort by
BR and replace the table so entries like S6.A1, S9.SG1, etc. match the rollup
(ensure S6.A1 shows 7.016 / 15 / 23604 as in the rollup), and consider adding a
small regeneration note or script reference so README.md stays in sync with
touch-points-rollup.yaml.
docs/architecture/extension-api-v2/scripts/fetch-stars.sh-11-14 (1)

11-14: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preflight jq before the first success path.

The script exits cleanly when gh is missing, but a missing jq fails only after the first successful response and can leave a partially written cache. Check both tools up front.

Suggested fix
 if ! command -v gh >/dev/null 2>&1; then
   echo "❌ gh CLI not installed"
   exit 1
 fi
+
+if ! command -v jq >/dev/null 2>&1; then
+  echo "❌ jq not installed"
+  exit 1
+fi

Also applies to: 39-43

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/architecture/extension-api-v2/scripts/fetch-stars.sh` around lines 11 -
14, Add a preflight check for jq alongside the existing gh check in
fetch-stars.sh so both tools are verified before any network calls or cache
writes; specifically, ensure the script verifies command -v gh and command -v jq
early (the same place that currently checks gh) and fail fast with a clear error
and exit if either is missing (also update the duplicate check around the block
referenced by the second occurrence to avoid late failures and partial cache
writes).
docs/architecture/extension-api-v2/scripts/merge-staging-pass3.py-24-31 (1)

24-31: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Point this merger at the canonical database file.

fetch-stars.sh and rollup-blast-radius.py both read touch-points-database.yaml, but this script writes research/touch-points/database.yaml. As written, pass-3 merges will not feed the dataset that the rest of the pipeline and CI consume.

Suggested fix
-DB = ROOT / "research" / "touch-points" / "database.yaml"
+DB = ROOT / "touch-points-database.yaml"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/architecture/extension-api-v2/scripts/merge-staging-pass3.py` around
lines 24 - 31, The DB constant is pointing to "database.yaml" but the canonical
dataset used by fetch-stars.sh and rollup-blast-radius.py is
"touch-points-database.yaml"; update the DB assignment (the DB variable near
ROOT and STAGING) to reference the canonical file name
"touch-points-database.yaml" so pass-3 merges feed the same dataset the rest of
the pipeline/CI consume (leave R8, R9_SEC, R9_GUIDES, R9_CK unchanged).
src/extension-api-v2/__tests__/bc-13.v1.test.ts-14-53 (1)

14-53: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Implement executable tests for BC.13 v1 serialization contract.

This suite currently contains only pending it.todo(...) placeholders with no executable assertions. Per coding guidelines, tests must be written for all changes, especially for high-blast-radius features like BC.13 v1's per-node serialization interception. The empty suite passes CI without validating any behavior—prototype.serialize patching, onSerialize callbacks, or the NaN→null silent corruption edge cases remain untested at runtime.

Replace at least the critical paths (prototype.serialize chaining, onSerialize mutation, NaN coercion) with executable test implementations.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/bc-13.v1.test.ts` around lines 14 - 53,
Replace the it.todo placeholders with real assertions that exercise the BC.13 v1
serialization contract: implement tests that (1) patch
node.constructor.prototype.serialize and call the original via
origSerialize.call(this) to assert the returned object is the base serialization
and that added custom fields are persisted to the workflow JSON on disk (use the
same describe block and test title text to locate the case), (2) assign
node.onSerialize = fn and assert fn is invoked with the serialization data
object after base serialize, that fn may mutate data.myData in place and that
mutation appears in the saved JSON, and (3) create a widget whose serializeValue
returns NaN and assert JSON.stringify coerces it to null in widgets_values and
that restoring the graph reads null back (use symbols graphToPrompt,
node.onSerialize, serializeValue, and prototype.serialize to find relevant
helpers/mocks); make each test perform setup, call the real serialization path
(e.g., graphToPrompt or save/restore helper), and include explicit expect
assertions instead of todos.
docs/architecture/extension-api-v2/scripts/rollup-blast-radius.py-63-67 (1)

63-67: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Don't use severity as the silent-breakage input.

The plan defines this term from the worst observed breakage_class in evidence, but the current fallback maps CRITICAL/HIGH/... into the silent-breakage slot. That conflates impact with failure mode and skews the ranking even when the actual breakage mode is only loud or crash-only.

Suggested direction
-        sev_map = {"CRITICAL": 2, "HIGH": 1.5, "MEDIUM": 1, "LOW": 0.5}
-        silent_w = float(h.get("silent_breakage", sev_map.get(p.get("severity", ""), 0)))
+        breakage_weights = {
+            "silent": 1.0,
+            "undefined-behavior": 0.6,
+            "loud": 0.3,
+            "crash": 0.2,
+        }
+        silent_w = float(
+            h.get(
+                "silent_breakage",
+                max(
+                    (
+                        breakage_weights.get(e.get("breakage_class"), 0)
+                        for e in evidence
+                    ),
+                    default=0,
+                ),
+            )
+        )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/architecture/extension-api-v2/scripts/rollup-blast-radius.py` around
lines 63 - 67, The code incorrectly uses p.get("severity") when computing
silent_w, conflating impact with breakage mode; change the fallback for the
silent_breakage lookup so sev_map is keyed from p.get("breakage_class") (or use
p.get("breakage_class","") as the lookup key) instead of p.get("severity"),
leaving the explicit h.get("silent_breakage") override intact; update the line
that computes silent_w (referencing variables p, h, sev_map, and silent_w) to
map breakage_class→sev_map for the default.
docs/architecture/extension-api-v2/scripts/merge-staging-pass3.py-36-45 (1)

36-45: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Replace both eval(...) calls with ast.literal_eval(...).

These values come from YAML staging files. eval() remains an execution primitive here even with restricted builtins, and the blanket except Exception masks malformed input. literal_eval() plus (SyntaxError, ValueError) handling safely parses literal Python structures without executing arbitrary expressions.

Occurs at lines 40 and 132.

Suggested fix
+from ast import literal_eval
 import sys
 from pathlib import Path
@@
 def normalize_lines(lines):
     if isinstance(lines, str):
-        # R8 emitted strings like "[119, 131]" — convert
         try:
-            return tuple(eval(lines, {"__builtins__": {}}, {}))
-        except Exception:
+            return tuple(literal_eval(lines))
+        except (SyntaxError, ValueError):
             return (lines,)
@@
         evidence_field = sp.get("evidence")
         if isinstance(evidence_field, str):
             try:
-                evidence_field = eval(evidence_field, {"__builtins__": {}}, {})
-            except Exception:
+                evidence_field = literal_eval(evidence_field)
+            except (SyntaxError, ValueError):
                 evidence_field = []
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/architecture/extension-api-v2/scripts/merge-staging-pass3.py` around
lines 36 - 45, The code uses eval(...) in two places (e.g., inside
normalize_lines) to parse YAML-derived strings—replace both eval calls with
ast.literal_eval and import ast at the top if not present; change the broad
except Exception to only catch (SyntaxError, ValueError) and fall back to
returning (lines,) to preserve current behavior while avoiding execution of
arbitrary code and not masking unrelated errors.
src/services/extensionV2Service.ts-132-133 (1)

132-133: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Do not hand out live ECS references from these getters.

getOptions(), getPosition(), and getSize() currently return the exact objects stored in the World. An extension can mutate those in place and bypass dispatch/undo/CRDT propagation entirely. These should return shallow snapshots, the same way getProperties() already does.

Proposed fix
         getOptions(): WidgetOptions {
-            return world.getComponent(widgetId, WidgetValue).options ?? {}
+            return {
+                ...(world.getComponent(widgetId, WidgetValue).options ?? {})
+            }
         },
@@
         getPosition(): Point {
-            return world.getComponent(nodeId, Position).pos
+            return [...world.getComponent(nodeId, Position).pos] as Point
         },
         getSize(): Size {
-            return world.getComponent(nodeId, Dimensions).size
+            return [...world.getComponent(nodeId, Dimensions).size] as Size
         },

As per coding guidelines, "Every entity state change must be a serializable, idempotent, deterministic command (Command pattern) - replayable, undoable, and transmittable over CRDT."

Also applies to: 183-188

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/extensionV2Service.ts` around lines 132 - 133, getOptions(),
getPosition(), and getSize() currently return live references from
world.getComponent(widgetId, WidgetValue).options/position/size; change each to
return a shallow snapshot instead (e.g., create and return a shallow copy like {
...orig } or Array.from for arrays) so callers cannot mutate ECS state directly.
Locate the getters named getOptions, getPosition, and getSize and replace their
return expressions to return a cloned object/array of the corresponding
WidgetValue field (use the same snapshot pattern as getProperties()), ensuring
you reference widgetId and world.getComponent(widgetId, WidgetValue) to read the
source before copying. Ensure the snapshots are shallow (not deep) to match
existing behavior and preserve serializability/command patterns.
src/services/extensionV2Service.ts-317-326 (1)

317-326: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

defineWidgetExtension() is currently inert.

The registry stores widget extensions, but the mount system only ever iterates nodeExtensions. widgetCreated() is never invoked, and the returned render/destroy lifecycle is never wired up, so consumers can successfully register a public widget extension that silently never runs.

Also applies to: 346-378, 390-413

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/extensionV2Service.ts` around lines 317 - 326, The widget
registry currently collects entries into widgetExtensions via
defineWidgetExtension but the mount flow only iterates nodeExtensions and never
calls widgetCreated or wires up the render/destroy lifecycle; update the
mount/initialization code that currently iterates nodeExtensions to also iterate
widgetExtensions (or create a parallel iteration) so that for each
WidgetExtensionOptions you call its widgetCreated hook and register its returned
render and destroy callbacks into the same lifecycle management as nodes; locate
where nodeExtensions is consumed and add analogous handling for widgetExtensions
so widgetCreated, render and destroy are invoked and cleaned up correctly.
src/types/extensionV2.ts-133-143 (1)

133-143: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Add a typed teardown hook before freezing NodeExtensionOptions.

NodeExtensionOptions currently has creation hooks only, but the BC.02 v2/migration scaffolds in this PR already require onRemoved(handle). Without a first-class teardown callback, extensions have no typed place to release timers, observers, or other node-scoped resources on deletion.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/extensionV2.ts` around lines 133 - 143, Add a typed teardown hook
to NodeExtensionOptions so extensions can release node-scoped resources: update
the NodeExtensionOptions interface to include an optional onRemoved(node:
NodeHandle): void method (mirroring nodeCreated/loadedGraphNode signatures)
before the interface is frozen; ensure the new onRemoved is exported with the
interface so migration scaffolds and runtime callers can call
extension.onRemoved(handle) when a node is deleted.
src/types/extensionV2.ts-118-129 (1)

118-129: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

BC.04 interaction events are missing from the published NodeHandle surface.

The new compat scaffolds in this PR already treat mousedown, selected/deselected, and resize as canonical v2 events, but the stable type surface only exposes positionChanged/sizeChanged/modeChanged. Shipping Phase A like this means a follow-up PR has to add new public overloads later or keep the spec/tests permanently ahead of the contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/types/extensionV2.ts` around lines 118 - 129, The NodeHandle type
currently only exposes on(...) overloads for
positionChanged/sizeChanged/modeChanged but misses the compat event overloads
used elsewhere; add public on(...) overloads for the interaction events used by
the new scaffolds—specifically include on(event: 'mousedown', fn: (e: MouseEvent
| {button:number, x:number, y:number}) => void): void, on(event:
'selected'|'deselected', fn: () => void): void, and on(event: 'resize', fn:
(size: Size) => void): void (alongside existing on signatures) so the NodeHandle
interface (where positionChanged/sizeChanged/modeChanged are declared) exposes
the same canonical v2 events as the runtime scaffolds.
src/services/extensionV2Service.ts-390-412 (1)

390-412: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add immediate: true to watch options to mount extensions for pre-existing nodes.

Without immediate: true, the watcher is lazy and skips the callback on initial setup. This means any nodes that already exist in the World when startExtensionSystem() runs—preloaded workflows, bootstrap-time nodes—won't have extensions mounted until world.queryAll(NodeType) changes again.

Fix
     watch(
         () => world.queryAll(NodeType),
         (currentIds, previousIds) => {
             const prev = new Set(previousIds ?? [])
             const curr = new Set(currentIds)

             for (const nodeId of currentIds) {
                 if (!prev.has(nodeId)) {
                     mountExtensionsForNode(nodeId)
                 }
             }

             for (const nodeId of previousIds ?? []) {
                 if (!curr.has(nodeId)) {
                     unmountExtensionsForNode(nodeId)
                 }
             }
         },
-        { flush: 'post' }
+        { flush: 'post', immediate: true }
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/services/extensionV2Service.ts` around lines 390 - 412, The watcher in
startExtensionSystem currently omits immediate:true so pre-existing nodes are
skipped; update the watch call in startExtensionSystem (the watch of () =>
world.queryAll(NodeType)) to include immediate: true in the options alongside
flush: 'post' so the callback runs once at setup and mountExtensionsForNode is
invoked for existing nodes; keep the existing logic for unmounting unchanged.
package.json-50-52 (1)

50-52: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

CI is blocked by lockfile/specifier drift; sync lockfiles before merge.

The new scripts are fine, but this PR is currently non-mergeable because multiple jobs fail on --frozen-lockfile with specifier mismatches. Please regenerate and commit the lockfile updates for all touched package manifests in this stack.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@package.json` around lines 50 - 52, CI is failing due to lockfile/specifier
drift caused by changes to package manifests (e.g., the new scripts like
"test:extension-api", "test:extension-api:watch", "test:extension-api:coverage"
in package.json); regenerate the lockfiles for every affected package manifest
by running your package manager's install/lockfile update (e.g., npm/yarn/pnpm
install or pnpm -w install) so the lockfile matches the updated package.json,
verify builds pass locally with the same flags used in CI (including
--frozen-lockfile), and commit the updated lockfile(s) alongside the
package.json changes before pushing.
src/extension-api-v2/__tests__/v2/BC.18.v2.test.ts-5-9 (1)

5-9: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Compat-floor is declared but currently non-enforcing.

Line 6 through Line 8 are all it.todo(...), so this suite can’t fail when BC.18 behavior regresses. For compat-floor coverage, at least one executable assertion per contract branch should be included in this PR (or gate merge on follow-up completion).

As per coding guidelines, "Write tests for all changes, especially bug fixes to catch future regressions."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/v2/BC.18.v2.test.ts` around lines 5 - 9, The
test suite currently uses only it.todo entries so compat-floor isn't enforced;
replace at least one it.todo in BC.18 v2 — Backend HTTP calls with an executable
test that asserts the expected behavior (for example, change the
'defineExtension setup exposes an api object with a fetchApi(path, init?)
method' todo into an actual it(...) that calls the extension setup helper and
asserts the returned api object has a fetchApi function and that calling it
returns a Promise-like Response); target the test in
src/extension-api-v2/__tests__/v2/BC.18.v2.test.ts and reference the
defineExtension/setup helper and the api.fetchApi symbol so the test fails on
regressions and satisfies the compat-floor requirement.
src/extension-api-v2/__tests__/bc-01.v2.test.ts-11-40 (1)

11-40: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Replace TODO-only contract placeholders with at least one runnable assertion per contract block before merge.

From Line 11 onward, the suite is entirely it.todo(...), so it can’t detect BC.01 regressions even when CI is green. Keep TODOs for backlog depth, but add minimal executable assertions now for lifecycle ordering and handle semantics.

Example pattern to start converting placeholders
- it.todo(
-   'nodeCreated is called once per node instance and receives a NodeHandle wrapping the created node'
- )
+ it(
+   'nodeCreated is called once per node instance and receives a NodeHandle wrapping the created node',
+   async () => {
+     // arrange test graph + extension registration
+     // create one node instance
+     // assert handler called once with a NodeHandle
+   }
+ )

As per coding guidelines: "Write tests for all changes, especially bug fixes to catch future regressions".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/bc-01.v2.test.ts` around lines 11 - 40, The
test file contains only it.todo placeholders so BC.01 checks never run; replace
at least one todo in each contract block with a minimal runnable assertion: in
the "nodeCreated(handle) — per-instance setup" describe, replace one it.todo
with an it that invokes the node lifecycle or a minimal smoke assertion
asserting nodeCreated is called or NodeHandle properties exist (reference
nodeCreated, NodeHandle.setState, NodeHandle.id, NodeHandle.vueRef); in the
"type-level registration (replacement for S2.N8)" describe, replace one it.todo
with an it that registers a simple defineNodeExtension({ types: [...] }) or
omits types and asserts the appropriate handler was invoked for registered vs
unregistered types (reference defineNodeExtension and type-scoped registration);
keep other todos, make assertions minimal and deterministic so CI fails on
regressions.
packages/extension-api/scripts/build-docs.ts-405-410 (1)

405-410: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use argv-based process invocation instead of shell command strings for running TypeDoc.

The current execSync with a shell command string (npx typedoc --options ${...}) creates an unnecessary command-injection risk and lacks error handling. Pass command arguments as an array instead so paths are treated as data, not shell syntax.

🔧 Proposed fix
-import { execSync } from 'node:child_process'
+import { spawnSync } from 'node:child_process'

 function runTypedoc(): void {
   console.log('▶ Running TypeDoc...')
-  execSync(
-    `npx typedoc --options ${path.join(pkgRoot, 'typedoc.json')} --out ${rawDir}`,
-    { cwd: pkgRoot, stdio: 'inherit' }
-  )
+  const bin = process.platform === 'win32' ? 'npx.cmd' : 'npx'
+  const result = spawnSync(
+    bin,
+    [
+      'typedoc',
+      '--options',
+      path.join(pkgRoot, 'typedoc.json'),
+      '--out',
+      rawDir
+    ],
+    { cwd: pkgRoot, stdio: 'inherit' }
+  )
+  if (result.status !== 0) {
+    throw new Error(
+      `TypeDoc failed with exit code ${String(result.status ?? 'unknown')}`
+    )
+  }
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension-api/scripts/build-docs.ts` around lines 405 - 410, The
runTypedoc function currently calls execSync with a single shell command string
which risks command injection; replace that call with a no-shell invocation
using child_process.spawnSync or child_process.execFileSync, passing "npx" (or
the typedoc binary path) as the command and an args array like
["typedoc","--options", path.join(pkgRoot,'typedoc.json'), "--out", rawDir] (or
["typedoc", "--options", ..., "--out", ...] if invoking the binary directly) and
keep stdio: 'inherit'; also wrap the call in try/catch to handle and surface
errors (log via console.error and rethrow or exit) so failures in runTypedoc are
properly reported.
.github/workflows/ci-tests-extension-api.yaml-15-37 (1)

15-37: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Add job-level path conditionals instead of trigger-level filters.

This workflow uses on.push.paths and on.pull_request.paths to filter triggers. If these jobs are (or will be) required status checks on main, this pattern prevents check runs from being created on docs-only PRs, leaving checks stuck as pending. Move path filtering to job-level if: conditions so GitHub still creates check runs but marks them as skipped when path conditions aren't met.

For example:

jobs:
  test:
    if: |
      contains(fromJson('["src/extension-api/**", "src/extension-api-v2/**", "packages/extension-api/**", "vitest.extension-api.config.mts", "research/touch-points/rollup.yaml", "research/touch-points/behavior-categories.yaml", "scripts/check-compat-floor.py", "pnpm-lock.yaml"]'), github.event.pull_request.head.ref) || github.event_name == 'push'
    runs-on: ubuntu-latest
    # ... rest of job

Alternatively, use a workflow-level output or matrix strategy to centralize path logic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci-tests-extension-api.yaml around lines 15 - 37, The
workflow currently uses trigger-level path filters (on.push.paths and
on.pull_request.paths) which prevent GitHub from creating check runs for PRs;
change to job-level path conditionals by removing the paths entries under
on.push and on.pull_request and adding an if: condition on each job (e.g., the
test job) that evaluates whether the event is a push or the PR touches the
desired paths (use contains(fromJson(...), <path>) or equivalent logic combined
with github.event_name == 'push'); ensure each job (for example the job named
test or any jobs under jobs:) uses that if: so checks are created but skipped
when irrelevant.

Comment on lines +255 to +256
if "evidence-sweep-pass-2" not in db["meta"].get("sweeps_done", []):
db["meta"]["sweeps_done"].append("evidence-sweep-pass-2")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Initialize meta.sweeps_done before appending to avoid a runtime crash.

If sweeps_done is missing, Line 256 throws KeyError even though Line 255 uses .get(...).

🐛 Proposed fix
-    if "evidence-sweep-pass-2" not in db["meta"].get("sweeps_done", []):
-        db["meta"]["sweeps_done"].append("evidence-sweep-pass-2")
+    sweeps_done = db["meta"].setdefault("sweeps_done", [])
+    if "evidence-sweep-pass-2" not in sweeps_done:
+        sweeps_done.append("evidence-sweep-pass-2")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if "evidence-sweep-pass-2" not in db["meta"].get("sweeps_done", []):
db["meta"]["sweeps_done"].append("evidence-sweep-pass-2")
sweeps_done = db["meta"].setdefault("sweeps_done", [])
if "evidence-sweep-pass-2" not in sweeps_done:
sweeps_done.append("evidence-sweep-pass-2")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/architecture/extension-api-v2/scripts/add-evidence-pass2.py` around
lines 255 - 256, The code assumes db["meta"]["sweeps_done"] exists before
appending, causing a KeyError when it's missing; update the block around
db["meta"] and "sweeps_done" to initialize sweeps_done as an empty list if
absent (e.g., use db["meta"].setdefault("sweeps_done", []) or check/get and
assign an empty list) before checking membership and appending
"evidence-sweep-pass-2" so db["meta"]["sweeps_done"].append(...) never raises.

Comment on lines +16 to +21
"devDependencies": {
"tsx": "catalog:",
"typedoc": "0.28.19",
"typedoc-plugin-markdown": "^4.6.3",
"typescript": "catalog:"
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Critical: Update pnpm-lock.yaml to reflect new devDependencies.

All pipeline failures report lockfile mismatch. After adding these devDependencies, you must run pnpm install at the repository root to update pnpm-lock.yaml. The frozen-lockfile CI checks will continue to fail until the lockfile is synchronized.

🔧 Resolution steps
# From repository root:
pnpm install
git add pnpm-lock.yaml
git commit -m "chore: update lockfile for extension-api devDependencies"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/extension-api/package.json` around lines 16 - 21, The repo lockfile
is out of sync after adding devDependencies ("tsx" and "typescript" entries) to
packages/extension-api package.json; run pnpm install from the repository root
to regenerate pnpm-lock.yaml, stage the updated pnpm-lock.yaml, and commit
(e.g., commit message like "chore: update lockfile for extension-api
devDependencies") so CI frozen-lockfile checks pass.

Comment on lines +6 to +9
it.todo('v1 nodeType.prototype.onConnectionsChange fires with the same (type, index, connected, linkInfo) args as v2 connectionChanged event')
it.todo('v1 nodeType.prototype.onConnectInput returning false vetoes the connection identically to v2 connectInput returning false')
it.todo('v1 nodeType.prototype.onConnectOutput returning false vetoes the connection identically to v2 connectOutput returning false')
it.todo('v2 connectionChanged event fires once per connect and once per disconnect, matching v1 onConnectionsChange invocation parity')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify naming consistency for BC.07 connection events in extension-api test scaffolds.
rg -n --type=ts -C2 'connectionChanged|connectionChange|connectInput|connectOutput' src/extension-api-v2/__tests__

Repository: Comfy-Org/ComfyUI_frontend

Length of output: 14878


Update BC.07.migration.test.ts to use canonical event name 'connectionChange' before implementing.

The v2 contract uses 'connectionChange' (not 'connectionChanged'). Lines 6 and 9 currently encode the wrong parity target. The canonical event name is confirmed across bc-07.v2.test.ts and bc-08.v2.test.ts. Correct the event names in the TODO descriptions before implementation to ensure migration parity tests target the correct v2 API surface.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/extension-api-v2/__tests__/migration/BC.07.migration.test.ts` around
lines 6 - 9, Update the TODO descriptions in BC.07.migration.test.ts that
reference the v2 event name: replace the incorrect 'connectionChanged' with the
canonical 'connectionChange' so the parity targets the right v2 API;
specifically edit the two TODO strings (the one comparing v1 onConnectionsChange
parity and the one asserting event firing per connect/disconnect) to use
'connectionChange' to match bc-07.v2.test.ts and bc-08.v2.test.ts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant