Migrate all window.devicePixelRatio reads to use LGraphCanvas.dpr,
the single source of truth set by applyViewport.
LGraphCanvas:
- Add dpr property, initialized in constructor, updated on resize()
- Migrate 6 internal DPR reads: drawFrontCanvas compositing,
drawBackCanvas transform reset, centerOnNode, renderInfo,
processMouseDown hit-testing, LOD threshold calculation
- Mark resize() as @deprecated
External consumers:
- litegraphService.getCanvasCenter(): use canvas.dpr
- useMinimapViewport: use canvas.dpr
- layoutStore: documented TODO (no canvas reference at this layer)
- DragAndScale: documented exception (no canvas reference)
app.ts:
- Mark resizeCanvas() as @deprecated
- Inline viewport calls in scheduler path
- Set canvas.dpr after applyViewport
canvasViewport.ts:
- Export CanvasViewport type
- applyViewport returns the viewport for chaining
ADR 0009: Proposed -> Accepted
- Uncomment canvasNotMeasurable guards in DragAndScale fitToBounds/animateToBounds
- Make LGraphCanvas.resize() delegate to measureViewport/applyViewport
- Move devAssert to src/base/common/ (DDD layer compliance)
- Enhance devAssert with pluggable reporter hook; wire Sentry + toast in main.ts
- Set ADR 0009 status to Proposed (pending full migration in follow-up)
Two independent resize paths existed: resizeCanvas() in app.ts (DPR-aware)
and LGraphCanvas.resize() (DPR-unaware). Neither documented its dependency
on the other, creating implicit temporal coupling. The bg/fg canvas size
mismatch occurred because drawFrontCanvas() composites the bg canvas by
dividing its dimensions by DPR — assuming both canvases were DPR-scaled —
but LGraphCanvas.resize() set them to CSS pixels.
Introduce CanvasViewport: a plain frozen data object (ECS component style)
that serves as single source of truth for canvas sizing. measureViewport()
is a pure function producing viewport state from DOM measurements.
applyViewport() atomically sizes both fg and bg canvases and scales their
contexts, eliminating the possibility of a partial resize.
- Add devAssert() utility: throws in DEV, console.errors in prod
- Add fg/bg size invariant assertion in LGraphCanvas.draw()
- Make LGraphCanvas.resize() DPR-aware (scales both canvases + contexts)
- Replace resizeCanvas() internals with viewport system
- Wire viewport resize into canvasScheduler for app-mode transitions
- 13 new unit tests for viewport and assert modules
## 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