## Summary
Prototype: badge rows are derived presentation, not entity state, so
this replaces the materialized `nodeBadgeStore` (and its
registration/watcher/scope machinery, which amounted to cache
invalidation) with a per-node memoized derivation.
## Changes
- **What**: `nodeBadges(node)` — one lazy `computed` per node instance
in a `WeakMap`; entries die with their nodes, so registration, teardown,
and the stale-bucket bug class do not exist. A single
`graphStructureRevision` (litegraph leaf, bumped at
`add`/`remove`/`clear` chokepoints and structure events) replaces bucket
membership, `subgraphCreditsRevision`, and the
`resolveGraphId`/`resolveNode` seams. Litegraph consumes rows through a
one-function `setBadgeRowsProvider` seam, keeping its import graph
acyclic. `computeBadges` stays pure/plain-data; the identity-keyed draw
cache is unchanged. Net −456 lines.
- **Breaking**: none beyond the parent PR; `node.badges` and settings
behavior unchanged.
## Review Focus
- ECS-goal compliance despite deleting the store: authoritative truth
stays in the source stores + graph; badges were a projection, and the
design-doc amendment in `docs/architecture/node-badge-store.md`
("Prototype: derive-on-read") records the reasoning and trade-offs.
- Coarse invalidation: any structural change invalidates every badge
computed (flag only; recompute is lazy per read). Stress case is
large-workflow drag/paste.
- Legacy-canvas redraw on async price resolution still rides the
`pricingRevision` watch in `useNodeBadge`, same as the parent branch.
Child of #13460 — compare against `drjkl/and-then-there-were-none`, not
`feature/ecs-migration`.
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary
Extracts node badges into a dedicated store per ADR 0008 — slice A of
`docs/architecture/node-badge-store.md`: store, plain `BadgeData` rows,
reactive badge system, and LGraph chokepoint registration. Stacked on
#13455.
## Changes
- **What**:
- `src/types/badgeData.ts` — plain `BadgeData { kind, text, fgColor?,
bgColor?, iconKey? }` rows (decision 1).
- `src/stores/nodeBadgeStore.ts` — root-scoped buckets keyed by
`NodeId`, kind-ordered reads, identity-checked row deletes,
`registerNode`/`unregisterNode`/`clearGraph` registration trio; row
writes refused for unregistered nodes (decision 2).
- `src/systems/badgeSystem.ts` — pure `computeBadges(sources)` +
effect-scope-per-node shell watching `registeredNodeIds`; writes core
and credits rows from
nodeDef/settings/palette/pricing/widget-value/link-connectivity sources
(decisions 3–4).
- `LGraph.add`/`remove`/`clear` + subgraph-definition GC maintain the
store registration (linkStore/rerouteStore convention).
- Design record updated with implementation notes.
- **Breaking**: none yet — `node.badges`/`useNodeBadge` closures
untouched; nothing starts the system in production until slice B's
consumer cutover.
## Review Focus
- Registration is bucket-key presence; the system discovers nodes by
watching `registeredNodeIds`, so litegraph never imports pricing/nodeDef
modules (no import cycle via the litegraph barrel).
- Core rows are fine-grained (lifecycle, id, source) under one
visibility rule — unification notes in the design record (Vue gains
HideBuiltIn on id badges; legacy join/truncation becomes slice-B
draw-cache presentation).
- Subgraph credits aggregation, the legacy `node.badges` surface, and
`badgePosition` are open decisions in the design record and deliberately
not in this PR.
- Slice A+B together would exceed the ~300-line guideline, hence the
split; store row APIs (`registerBadge`/`deleteBadge`) land now because
the slice-B shim consumes them.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
## Summary
Follow-up cleanups from the reroute chain store work (#13449): deletes
the `slot._floatingLinks` mirror in favor of endpoint derivation, fixes
two position/endpoint integrity gaps the mirror had masked, and
restructures the subgraph-unpack chain stitching.
Stacked on #13449.
## Changes
- **What**:
- `slot._floatingLinks` Sets deleted — a floating link's attachment is
fully encoded in its own endpoints, so `slotFloatingLinks(network, side,
nodeId, slot)` derives it; ~25 write sites across
`addFloatingLink`/`removeFloatingLink`, `setFloatingLinkOrigin`, and
every `FloatingRenderLink` connect method are gone
- `moveInputLink`/`moveOutputLink` gain the `node` param their
`dragNewFrom*` siblings already take
- `FloatingRenderLink.connectToSubgraphOutput` now writes the link's
**target** end (it wrote `origin_id = SUBGRAPH_OUTPUT_ID`,
clobbering/skewing endpoints — masked by the mirror)
- `removeInput`/`removeOutput` renumber floating-link slot indices
alongside real links (stale indices previously persisted into serialized
floating links)
- `Reroute.snapToGrid` mirrors into the layout store like `move()` does
(Vue hit-testing read a stale spatial index after snapped drags)
- Subgraph-unpack reroute chain stitching rewritten as
collect-then-stitch (two segment walks + one generic pointer pass,
replacing three interleaved walk-and-write loops)
## Review Focus
- Scoped out with written assessments: `input.link`/`output.links`
mirror removal (roadmap-scale `SlotConnection` extraction, ~530
accesses) and reroute position ownership inversion (layout store only
seeds the active graph, no writeback)
- Corrupt-data error paths in unpack stitching are normalized to
per-link skip (the old code broke the whole loop on some
internal-segment failures)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
Extracts reroute connectivity into a dedicated `rerouteStore` per ADR
0008: chain state (`parentId`, floating marker) is stored by reference,
and link membership (`linkIds`/`floatingLinkIds`) is derived from the
links' `parentId` chains instead of being hand-maintained at ~10 write
sites.
Stacked on #13436 (link topology store); design record in
`docs/architecture/reroute-chain-store.md`.
## Changes
- **What**:
- `LLink._state` now holds the link store's reactive proxy (BaseWidget
pattern), so `link.parentId` writes are tracked by Vue
- New `rerouteStore`: root-scoped buckets keyed by `RerouteId`,
`RerouteChain {id, parentId?, floating?}` registered by reference;
`Reroute` class reads/writes through the store proxy
- Membership derived via a per-graph computed reverse index over link
topologies + chain states; `Reroute.linkIds`/`floatingLinkIds` become
readonly derived accessors
- Deleted: all manual membership mutation sites,
`Reroute.validateLinks`, `Reroute.removeLink`, `Reroute.update`'s
linkIds param
- `LGraph._addReroute`/`_removeReroute` chokepoints mirror
`_addLink`/`_removeLink`
- Load-time reroute-id dedup across sibling subgraph definitions
(mirrors node-id dedup; reroute ids must now be per-root-unique)
- Serialization emits `linkIds` in ascending link-id order; goldens
added for byte-identical round-trip
- **Breaking**: `Reroute.linkIds`/`floatingLinkIds` are now
`ReadonlySet` accessors — external mutation of these sets no longer
affects membership (the chain is the single source of truth).
`Reroute.validateLinks`/`removeLink` removed.
## Review Focus
- `LLink.disconnect` now unregisters the link before pruning empty
reroutes, so derived counts exclude it
- `dropOnReroute` resolves the drop target's source output once before
connecting moved links — re-anchoring the first link's chain changes
derived membership immediately (`LinkConnector.ts`)
- Workflows whose stored `linkIds` contradict their chains are repaired
rather than preserved (design decision 5; no shim)
---------
Co-authored-by: GitHub Action <action@github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: github-actions <github-actions@github.com>
## Summary
Realign the ECS architecture docs and ADR 0008 with the shipped
direction (PR #12617): entity data lives in dedicated Pinia stores keyed
by string IDs, rather than one unified \"World\" registry addressed by
branded entity IDs.
## Changes
- **What**: Docs-only. Retarget the `docs/architecture/` set + ADR 0008
+ agent guidance from the single-World/branded-`*EntityId` model to the
dedicated-store model that PR #12617 actually shipped
(`widgetValueStore` keyed by `WidgetId`, `layoutStore`,
`nodeOutputStore`, `domWidgetStore`, `subgraphNavigationStore`,
`previewExposureStore`).
- `AGENTS.md` + `.agents/checks/adr-compliance.md`: point entity-data
guidance at dedicated stores; fix the inverted `world.getComponent`
compliance check (it flagged correct store-based code).
- `ADR 0008`: dated amendment note (stays Proposed); rewrite the World
section → dedicated stores, Branded ID design (`WidgetId` composite
string), migration strategy, render-loop, consequences, notes.
- `proto-ecs-stores.md`: flip the \"Unified World / branded IDs\"
framing from gap-to-close → target; replace the deleted
`PromotedWidgetViewManager` with the `input.widgetId` store-backed
model; fix key formats and store count.
- `ecs-target-architecture.md` / `ecs-lifecycle-scenarios.md` /
`ecs-migration-plan.md`: reframe all `world.*` APIs and `*EntityId`
brands to per-store APIs + string keys; mark already-shipped migration
phases done.
- `subgraph-boundaries-and-promotion.md` / `entity-interactions.md` /
`entity-problems.md`: scope-tagged store entries; swap removed
`PromotionStore` for `previewExposureStore`.
- `appendix-critical-analysis.md`: post-pivot status banner + resolution
notes on the critiques the pivot vindicated; still-open gaps (extension
callbacks, atomicity, Y.js↔ECS) left live.
- `appendix-ecs-pattern-survey.md`: supersede banner; keep the external
library survey (§1).
- Delete obsolete `ecs-world-command-api.md` (its command-pattern
argument folded into ADR 0008).
- **Breaking**: None (documentation only).
## Review Focus
- ADR 0008 stays **Proposed** with an amendment note rather than a new
superseding ADR — confirm that's the preferred mechanism vs. a fresh
ADR.
- Numeric per-kind brands (`NodeEntityId`, `LinkEntityId`, …) are
retained in ADR 0008 but explicitly marked aspirational/unshipped; only
`WidgetId` (composite string) reflects shipped code.
- `appendix-ecs-pattern-survey.md` §2–§4 are kept under a supersede
banner as historical record (they describe the deleted `src/world/`
substrate) rather than rewritten — confirm that's preferred over
deletion.
- Net −384 lines; no code or test changes.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
Add the ECS pattern survey appendix to ADR 0008's companion-documents
table, and drop stale `PromotionStore` references across architecture
docs to reflect the ADR 0009 removal.
## Changes
- **What**:
- New `docs/architecture/appendix-ecs-pattern-survey.md` — surveys
bitECS, miniplex, koota, ECSY, Bevy, and Thyseus: patterns adopted,
departed from, and when to revisit.
- ADR 0008 companion table gains a row pointing at the new appendix.
- `docs/adr/0009-…/before-after-flows.md`,
`docs/architecture/ecs-target-architecture.md`,
`docs/architecture/entity-problems.md` — drop references to
`PromotionStore` / `usePromotionStore` (the legacy three-layer mechanism
is gone; promoted value widgets are now linked `SubgraphInput`s).
- `docs/architecture/subgraph-boundaries-and-promotion.md` — reframes
its "current mechanism" section as historical context with an explicit
"removed by ADR 0009" callout.
- **Breaking**: None — docs-only.
## Review Focus
Wording in the historical-context callout on
`subgraph-boundaries-and-promotion.md`. Everything else is a small
cleanup or a new standalone document.
---------
Co-authored-by: Amp <amp@ampcode.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: GitHub Action <action@github.com>
## 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>
*PR Created by the Glary-Bot Agent*
---
## Summary
ADR 008 (Entity Component System) referenced only 3 of 10 companion
architecture documents, making the rest undiscoverable to readers
browsing the design.
- Add inline contextual links in Context, Systems, and Migration
Strategy sections so readers encounter them while reading
- Add a comprehensive Supporting Documents table before Notes as a
complete index of all 10 companion docs
Previously unlinked files now referenced:
- `entity-interactions.md` — current entity relationship map
- `entity-problems.md` — structural problem catalog
- `proto-ecs-stores.md` — existing stores partially implementing ECS
- `ecs-target-architecture.md` — full target architecture
- `ecs-migration-plan.md` — phased migration roadmap
- `ecs-lifecycle-scenarios.md` — lifecycle operation walkthroughs
- `appendix-critical-analysis.md` — document accuracy verification
- `change-tracker.md` — current undo/redo system
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-11256-docs-add-hyperlinks-to-all-supporting-files-in-ADR-008-3436d73d365081828cf9ffa77e034f2d)
by [Unito](https://www.unito.io)
Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
## Summary
Fix 3D asset disappearing when switching between 3D and image outputs in
app mode — missing `onUnmounted` cleanup leaked WebGL contexts.
## Changes
- **What**: Add `onUnmounted` hook to `Preview3d.vue` that calls
`viewer.cleanup()`, releasing the WebGL context when Vue destroys the
component via its v-if chain. Add unit tests covering init, cleanup on
unmount, and remount behavior.
## Review Focus
When switching outputs in app mode, Vue's v-if chain destroys and
recreates `Preview3d`. Without `onUnmounted` cleanup, the old `Load3d`
instance (WebGL context, RAF loop, ResizeObserver) leaks. After ~8-16
toggles, the browser's WebGL context limit is exhausted and new 3D
viewers silently fail to render.
<!-- Pipeline-Ticket: e36489d2-a9fb-47ca-9e27-88eb3170836b -->
---------
Co-authored-by: Alexander Brown <drjkl@comfy.org>
## Summary
Documents the PrimitiveNode copy/paste bug mechanism and connection
lifecycle semantics in `WIDGET_SERIALIZATION.md`. This is tribal
knowledge from debugging
[#1757](https://github.com/Comfy-Org/ComfyUI_frontend/issues/1757) and
the related [Slack
discussion](https://comfy-organization.slack.com/archives/C09AQRB49QX/p1771806268469089).
## What's documented
- **The clone→serialize gap**: `_serializeItems()` calls
`item.clone()?.serialize()`. The clone has no `this.widgets`
(PrimitiveNode creates them on connection), so `serialize()` silently
drops `widgets_values`.
- **Why seed survives but control_after_generate doesn't**: Primary
widget value is copied from the target on reconnect; secondary widgets
read from `this.widgets_values` which was lost.
- **Current vs. proposed lifecycle**: Empty-on-copy → morph-on-connect
(current) vs. clone-configured-instance → empty-on-disconnect
(proposed).
- **Design considerations**: `input.widget` override flexibility,
deserialization ordering, and the minimal `serialize()` override fix.
## Related
- Issue: #1757
- Fix PR: #8938
- Companion: #9102 (initial WIDGET_SERIALIZATION.md), #9105 (type/JSDoc
improvements)
- Notion: COM-15282
┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-9119-docs-document-PrimitiveNode-copy-paste-semantics-and-widgets_values-loss-3106d73d3650816ba7f7d9e6f3bb3868)
by [Unito](https://www.unito.io)
* ADR: Add PrimeVue fork decision record
Adds ADR-0003 documenting the decision to fork PrimeVue as a monorepo workspace package. Key rationale includes transform coordinate system conflicts and virtual canvas scroll interference that require component-level modifications.
* ADR: Reject PrimeVue fork decision
- Change status from Proposed to Rejected
- Document rationale: implementation complexity with dual monorepos,
maintenance burden, alternative solutions available
- Add specific code citations and repository links
- Include alternative approach using shadcn/ui for selective replacement
- Rename 0004-crdt-based-layout-system.md to 0003-crdt-based-layout-system.md
- Update title from "4. Centralized..." to "3. Centralized..."
- Add ADR 0003 entry to docs/adr/README.md index table
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Alexander Brown <drjkl@comfy.org>
Adds ADR-0004 documenting the architectural decision to implement centralized layout management using CRDT backing store with command pattern architecture.
## Key Technical Decisions Documented
- **Centralized State Management**: Move from scattered `node.position` mutations to single authoritative layout store
- **CRDT Foundation**: Yjs-backed store provides conflict resolution and collaboration readiness
- **Command Pattern**: All spatial mutations flow through explicit commands for undo/redo and system coordination
- **Reactive Architecture**: Transition from O(n) diff-based change detection to O(1) signal-based reactivity
## Current Architecture Problems Addressed
- Performance bottlenecks from polling-based change detection in complex workflows
- Position conflicts between LiteGraph canvas and DOMwidgets.ts overlay systems
- Inability to support collaborative editing due to direct mutation patterns
- Renderer lock-in preventing alternative rendering backends