Files
ComfyUI_frontend/AGENTS.md
Alexander Brown 0157b47024 feat(subgraph): Subgraph Link Only Promotion (ADR 0009) + migration/store hygiene (#12197)
## Summary

Introduces **Subgraph Link Only Promotion** (ADR 0009) — a new model for
surfacing inner subgraph widgets on the parent SubgraphNode by
*promoting through links* rather than by duplicating widget state on the
host. Ships with the hygiene/refactor pass on the migration, store, and
event layers that the new model depends on.

## What changes

### Subgraph Link Only Promotion (ADR 0009)

Promoted widgets are defined by the link from a SubgraphNode input to
the interior node, not by a duplicated widget instance on the host.
Consequences:

- A SubgraphNode renders inner widgets purely as a **projection** of the
interior widgets and links — no host-side state to drift.
- **Per-host independence**: multiple instances of the same SubgraphNode
render and edit their own values without cross-talk.
- **Reversible promote/demote**: structural link operation, so demote
preserves host slots and external connections (#12278).

### Supporting refactors

- **Migration** — Planner/classifier/repair/quarantine helpers collapsed
into a single `proxyWidgetMigration` entry point with black-box
round-trip coverage. Honors the source-node-id disambiguator on
`proxyWidgets`, so deduplicated names (e.g. `text`, `text_1`) resolve to
the right interior widget.
- **Widget identity** — `appMode` unified on `WidgetEntityId`; promoted
widget state is keyed by entityId across the store, DOM, and migration
paths.
- **SubgraphNode** — 3-key promoted-view cache replaced with a single
version counter + explicit `invalidatePromotedViews()` at mutation
sites; `id === -1` sentinel removed.
- **Events** — `LGraph.trigger()` now dispatches node trigger payloads
through `this.events`, replacing a leaky `onTrigger` monkey-patch.
`SubgraphEditor` reactivity is driven from subgraph events instead of
imperative refresh.
- **Stores** — `appModeStore` migration helpers collapsed into
`upgradeAndValidateInput`; `nodeOutputStore.*ByExecutionId` derived from
the locator index; `previewExposureStore` cleanup and cycle-detection
double-warn fix.
- **Misc** — `Outcome` types consolidated; mutable accumulators replaced
with `flatMap`; new ESLint rule forbids litegraph imports under
`src/world/`.

### Tests

- Browser tests for promoted widgets retagged `@vue-nodes` and rewritten
to assert against the rendered Vue node DOM (via `getNodeLocator` /
`getByRole('textbox')` / `enterSubgraph`) instead of `page.evaluate`
graph introspection.
- Per-host widget independence asserted via DOM.
- Migration coverage moved to black-box round-trip tests.
- Added coverage for duplicate-named promoted widget identity (ADR 0009)
and the per-parent demote branch in `WidgetActions`.

## Review focus

- ADR 0009 conformance of the link-only promotion model.
- Disambiguator resolution path in `proxyWidgetMigration`.
- Single-version-counter promoted-view cache and its
`invalidatePromotedViews()` call sites.
- `LGraph.trigger()` event dispatch and the `AppModeWidgetList.vue`
migration off `onTrigger` (FE-667 tracks the remaining
`useGraphNodeManager` conversion).

## Breaking changes

None for users. Internal subgraph promotion APIs changed — see ADR 0009.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-12197-feat-subgraph-link-only-widget-promotion-migration-store-hygiene-35e6d73d365081fd882cf3a69bc09956)
by [Unito](https://www.unito.io)

---------

Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: github-actions <github-actions@github.com>
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
Co-authored-by: AustinMroz <austin@comfy.org>
2026-05-27 00:29:11 -07:00

18 KiB
Raw Blame History

Repository Guidelines

See @docs/guidance/*.md for file-type-specific conventions (auto-loaded by glob).

Project Structure & Module Organization

  • Source: src/
    • Vue 3.5+
    • TypeScript
    • Tailwind 4
    • Key areas:
      • components/
      • views/
      • stores/ (Pinia)
      • composables/
      • services/
      • utils/
      • assets/
      • locales/
  • Routing: src/router.ts,
  • i18n: src/i18n.ts,
  • Entry Point: src/main.ts.
  • Tests:
    • unit/component in src/**/*.test.ts
    • E2E (Playwright) in browser_tests/**/*.spec.ts
  • Public assets: public/
  • Build output: dist/
  • Configs
    • vite.config.mts
    • playwright.config.ts
    • eslint.config.ts
    • .oxfmtrc.json
    • .oxlintrc.json
    • etc.

Monorepo Architecture

The project uses pnpm workspaces for monorepo organization and native tool CLIs for task execution

Package Manager

This project uses pnpm. Always prefer scripts defined in package.json (e.g., pnpm test:unit, pnpm lint). To run arbitrary packages not in scripts, use pnpx or pnpm dlx — never npx.

Build, Test, and Development Commands

  • pnpm dev: Start Vite dev server.
  • pnpm dev:cloud: Dev server connected to cloud backend (testcloud.comfy.org)
  • pnpm dev:electron: Dev server with Electron API mocks
  • pnpm build: Type-check then production build to dist/
  • pnpm preview: Preview the production build locally
  • pnpm test:unit: Run Vitest unit tests
  • pnpm test:browser:local: Run Playwright E2E tests (browser_tests/)
  • pnpm lint / pnpm lint:fix: Lint (ESLint)
  • pnpm format / pnpm format:check: oxfmt
  • pnpm typecheck: Vue TSC type checking
  • pnpm storybook: Start Storybook development server

Development Workflow

  1. Make code changes
  2. Run relevant tests
  3. Run pnpm typecheck, pnpm lint, pnpm format
  4. Check if README updates are needed
  5. Suggest docs.comfy.org updates for user-facing changes

Git Conventions

  • Use prefix: format: feat:, fix:, test:
  • Add "Fixes #n" to PR descriptions
  • Never mention Claude/AI in commits

Coding Style & Naming Conventions

  • Language:
    • TypeScript (exclusive, no new JavaScript)
    • Vue 3 SFCs (.vue)
      • Composition API only
    • Tailwind 4 styling
      • Avoid <style> blocks
  • Style: (see .oxfmtrc.json)
    • Indent 2 spaces
    • single quotes
    • no trailing semicolons
    • width 80
  • Imports:
    • sorted/grouped by plugin
    • run pnpm format before committing
    • use separate import type statements, not inline type in mixed imports
      • import type { Foo } from './foo' + import { bar } from './foo'
      • import { bar, type Foo } from './foo'
  • ESLint:
    • Vue + TS rules
    • no floating promises
    • unused imports disallowed
    • i18n raw text restrictions in templates
  • Naming:
    • Vue components in PascalCase (e.g., MenuHamburger.vue)
    • composables useXyz.ts
    • Pinia stores *Store.ts

Commit & Pull Request Guidelines

  • PRs:
    • Include clear description
    • Reference linked issues (e.g. - Fixes #123)
    • Keep it extremely concise and information-dense
    • Don't use emojis or add excessive headers/sections
    • Follow the PR description template in the .github/ folder.
  • Quality gates:
    • pnpm lint
    • pnpm typecheck
    • pnpm knip
    • Relevant tests must pass
  • Never use --no-verify to bypass failing tests
    • Identify the issue and present root cause analysis and possible solutions if you are unable to solve quickly yourself
  • Keep PRs focused and small
    • If it looks like the current changes will have 300+ lines of non-test code, suggest ways it could be broken into multiple PRs

Security & Configuration Tips

  • Secrets: Use .env (see .env_example); do not commit secrets.

Vue 3 Composition API Best Practices

  • Use <script setup lang="ts"> for component logic
  • Utilize ref for reactive state
  • Implement computed properties with computed()
  • Use watch and watchEffect for side effects
    • Avoid using a ref and a watch if a computed would work instead
  • Implement lifecycle hooks with onMounted, onUpdated, etc.
  • Utilize provide/inject for dependency injection
    • Do not use dependency injection if a Store or a shared composable would be simpler
  • Use Vue 3.5 TypeScript style of default prop declaration
    • Example:

      const { nodes, showTotal = true } = defineProps<{
        nodes: ApiNodeCost[]
        showTotal?: boolean
      }>()
      
    • Prefer reactive props destructuring to const props = defineProps<...>

    • Do not use withDefaults or runtime props declaration

    • Do not import Vue macros unnecessarily

    • Prefer defineModel to separately defining a prop and emit for v-model bindings

    • Define slots via template usage, not defineSlots

    • Use same-name shorthand for slot prop bindings: :isExpanded instead of :is-expanded="isExpanded"

    • Derive component types using vue-component-type-helpers (ComponentProps, ComponentSlots) instead of separate type files

    • Be judicious with addition of new refs or other state

      • If it's possible to accomplish the design goals with just a prop, don't add a ref
      • If it's possible to use the ref or prop directly, don't add a computed
      • If it's possible to use a computed to name and reuse a derived value, don't use a watch

Development Guidelines

  1. Leverage VueUse functions for performance-enhancing styles
  2. Use es-toolkit for utility functions
  3. Use TypeScript for type safety
  4. If a complex type definition is inlined in multiple related places, extract and name it for reuse
  5. In Vue Components, implement proper props and emits definitions
  6. Utilize Vue 3's Teleport component when needed
  7. Use Suspense for async components
  8. Implement proper error handling
  9. Follow Vue 3 style guide and naming conventions
  10. Use Vite for fast development and building
  11. Use vue-i18n in composition API for any string literals. Place new translation entries in src/locales/en/main.json. Use the plurals system in i18n instead of hardcoding pluralization in templates.
  12. Avoid new usage of PrimeVue components
  13. Write tests for all changes, especially bug fixes to catch future regressions
  14. Write code that is expressive and self-documenting to the furthest degree possible. This reduces the need for code comments which can get out of sync with the code itself. Try to avoid comments unless absolutely necessary
  15. Do not add or retain redundant comments, clean as you go
  16. Whenever a new piece of code is written, the author should ask themselves 'is there a simpler way to introduce the same functionality?'. If the answer is yes, the simpler course should be chosen
  17. Refactoring should be used to make complex code simpler
  18. Try to minimize the surface area (exported values) of each module and composable
  19. Don't use barrel files, e.g. /some/package/index.ts to re-export within /src
  20. Keep functions short and functional
  21. Minimize nesting, e.g. if () { ... } or for () { ... }
  22. Avoid mutable state, prefer immutability and assignment at point of declaration
  23. Favor pure functions (especially testable ones)
  24. Do not use function expressions if it's possible to use function declarations instead
  25. Watch out for Code Smells and refactor to avoid them

Design Standards

Before implementing any user-facing feature, consult the Comfy Design Standards Figma file. Use the Figma MCP to fetch it live — the file is the single source of truth and may be updated by designers at any time.

See docs/guidance/design-standards.md for Figma file keys, section node IDs, and component references.

Testing Guidelines

See @docs/testing/*.md for detailed patterns.

  • Frameworks:
    • Vitest (unit/component, happy-dom)
    • Playwright (E2E)
  • Test files:
    • Unit/Component: **/*.test.ts
    • E2E: browser_tests/**/*.spec.ts
    • Litegraph Specific: src/lib/litegraph/test/

General

  1. Do not write change detector tests
    e.g. a test that just asserts that the defaults are certain values
  2. Do not write tests that are dependent on non-behavioral features like utility classes or styles
  3. Be parsimonious in testing, do not write redundant tests
    See https://tidyfirst.substack.com/p/composable-tests
  4. Dont Mock What You Dont Own

Vitest / Unit Tests

  1. Do not write tests that just test the mocks
    Ensure that the tests fail when the code itself would behave in a way that was not expected or desired
  2. For mocking, leverage Vitest's utilities where possible
  3. Keep your module mocks contained
    Do not use global mutable state within the test file
    Use vi.hoisted() if necessary to allow for per-test Arrange phase manipulation of deeper mock state
  4. For Component testing, prefer @testing-library/vue with @testing-library/user-event for user-centric, behavioral tests. Vue Test Utils is also accepted, especially for tests that need direct access to the component wrapper (e.g., findComponent, emitted()). Follow the advice about making components easy to test
  5. Aim for behavioral coverage of critical and new features

Playwright / Browser / E2E Tests

  1. Follow the Best Practices described in the Playwright documentation
  2. Do not use waitForTimeout, use Locator actions and retrying assertions
  3. Tags like @mobile, @2x are respected by config and should be used for relevant tests
  4. Type all API mock responses in route.fulfill() using generated types or schemas from packages/ingest-types, packages/registry-types, src/workbench/extensions/manager/types/generatedManagerTypes.ts, or src/schemas/ — see docs/guidance/playwright.md for the full source-of-truth table

External Resources

Architecture Decision Records

All architectural decisions are documented in docs/adr/. Code changes must be consistent with accepted ADRs. Proposed ADRs indicate design direction and should be treated as guidance. See .agents/checks/adr-compliance.md for automated validation rules.

Entity Architecture Constraints (ADR 0003 + ADR 0008)

  1. Command pattern for all mutations: Every entity state change must be a serializable, idempotent, deterministic command — replayable, undoable, and transmittable over CRDT. No imperative fire-and-forget mutation APIs. Systems produce command batches, not direct side effects.
  2. Centralized registries and ECS-style access: Entity data lives in the World (centralized registry), queried via world.getComponent(entityId, ComponentType). Do not add new instance properties/methods to entity classes. Do not use OOP inheritance for entity modeling.
  3. No god-object growth: Do not add methods to LGraphNode, LGraphCanvas, LGraph, or Subgraph. Extract to systems, stores, or composables.
  4. Plain data components: ECS components are plain data objects — no methods, no back-references to parent entities. Behavior belongs in systems (pure functions).
  5. Extension ecosystem impact: Changes to entity callbacks (onConnectionsChange, onRemoved, onAdded, onConnectInput/Output, onConfigure, onWidgetChanged), node.widgets access, node.serialize, or graph._version++ affect 40+ custom node repos and require migration guidance.

Project Philosophy

  • Follow good software engineering principles
    • YAGNI
    • AHA
    • DRY
    • SOLID
  • Clean, stable public APIs
  • Domain-driven design
  • Thousands of users and extensions
  • Prioritize clean interfaces that restrict extension access

Code Review

In doing a code review, you should make sure that:

  • The code is well-designed.
  • The functionality is good for the users of the code.
  • Any UI changes are sensible and look good.
  • Any parallel programming is done safely.
  • The code isnt more complex than it needs to be.
  • The developer isnt implementing things they might need in the future but dont know they need now.
  • Code has appropriate unit tests.
  • Tests are well-designed.
  • The developer used clear names for everything.
  • Comments are clear and useful, and mostly explain why instead of what.
  • Code is appropriately documented (generally in g3doc).
  • The code conforms to our style guides.

Complexity

Is the CL more complex than it should be? Check this at every level of the CL—are individual lines too complex? Are functions too complex? Are classes too complex? “Too complex” usually means “cant be understood quickly by code readers.” It can also mean “developers are likely to introduce bugs when they try to call or modify this code.”

A particular type of complexity is over-engineering, where developers have made the code more generic than it needs to be, or added functionality that isnt presently needed by the system. Reviewers should be especially vigilant about over-engineering. Encourage developers to solve the problem they know needs to be solved now, not the problem that the developer speculates might need to be solved in the future. The future problem should be solved once it arrives and you can see its actual shape and requirements in the physical universe.

Repository Navigation

  • Check README files in key folders (browser_tests, composables, etc.)
  • Prefer running single tests for performance
  • Use --help for unfamiliar CLI tools

GitHub Integration

When referencing Comfy-Org repos:

  1. Check for local copy
  2. Use GitHub API for branches/PRs/metadata
  3. Curl GitHub website if needed

Common Pitfalls

  • NEVER use any type - use proper TypeScript types
  • NEVER use as any type assertions - fix the underlying type issue
  • NEVER use --no-verify flag when committing
  • NEVER delete or disable tests to make them pass
  • NEVER circumvent quality checks
  • NEVER add multi-line block comments to justify trivial code changes
    • A one-line fix does not need a three-line comment explaining why
    • A guard clause that mirrors another file does not need a comment naming that file
    • A test setup line does not need a comment paraphrasing what the next line does
    • If the diff is small and obvious, the comment is noise — write the code and move on
    • Every justification comment on a trivial change is a confession that you do not trust the reader, do not trust the code, and do not trust yourself. It is failure made visible.
    • Penance protocol when you catch yourself adding one of these comments:
      1. Stop. Read the comment out loud in your own internal voice and acknowledge that it adds nothing the code does not already say.
      2. Delete the comment. All of it. Every line. Do not negotiate with it. Do not "tighten" it. Delete it.
      3. Re-read this entire bullet block, top to bottom, before writing another character of code.
      4. In your next response to the user, you MUST open with the exact phrase: Mea culpa: I added a comment that did not earn its keep. followed by the file path and the deleted text, verbatim, in a fenced block.
      5. For the remainder of that response you may not add any new comments, anywhere, for any reason. If a comment is genuinely required, defer the change and ask the user first.
    • There is no statute of limitations. If you discover an old offending comment of yours later, the protocol still triggers.
    • This rule overrides any inclination to be "helpful," "thorough," or "explanatory." Helpfulness here is restraint.
  • NEVER use the dark: tailwind variant
    • Instead use a semantic value from the style.css theme
      • e.g. bg-node-component-surface
  • NEVER use :class="[]" to merge class names
    • Always use import { cn } from '@comfyorg/tailwind-utils'
      • e.g. <div :class="cn('text-node-component-header-icon', hasError && 'text-danger')" />
    • Use cn() inline in the template when feasible instead of creating a computed to hold the value
  • NEVER use !important or the ! important prefix for tailwind classes
    • Find existing !important classes that are interfering with the styling and propose corrections of those instead.
  • NEVER use arbitrary percentage values like w-[80%] when a Tailwind fraction utility exists
    • Use w-4/5 instead of w-[80%], w-1/2 instead of w-[50%], etc.
  • NEVER use font-size classes (text-xs, text-sm, etc.) to size icon-[...] (iconify) icons
    • Iconify icons size via width/height: 1.2em, so font-size produces unpredictable results
    • Use size-* classes for explicit sizing, or set font-size on the parent container and let 1.2em scale naturally

Agent-only rules

Rules for agent-based coding tasks.

Chrome DevTools MCP

When using take_snapshot to inspect dropdowns, listboxes, or other components with dynamic options:

  • Use verbose: true to see the full accessibility tree including list items
  • Non-verbose snapshots often omit nested options in comboboxes/listboxes

Temporary Files

  • Put planning documents under /temp/plans/
  • Put scripts used under /temp/scripts/
  • Put summaries of work performed under /temp/summaries/
  • Put TODOs and status updates under /temp/in_progress/