Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
eca48a8
[docs] Add Vue node system architecture and implementation plans
christian-byrne Jun 25, 2025
0ec98e3
[feat] Add slot type color definitions
christian-byrne Jun 25, 2025
a041f40
[feat] Implement Vue-based node rendering components
christian-byrne Jun 25, 2025
065e292
[feat] Add TransformPane for Vue node coordinate synchronization
christian-byrne Jun 30, 2025
992d79b
[feat] Vue node lifecycle management implementation
christian-byrne Jul 2, 2025
3dc7686
[feat] Add widget renderer composable
christian-byrne Jul 2, 2025
95c291d
[fix] Fix WidgetSelect component for combo widgets
christian-byrne Jul 2, 2025
6bbd285
[feat] Update NodeWidgets to use safe widget data
christian-byrne Jul 2, 2025
39603dd
[feat] Update Vue node components with proper typing
christian-byrne Jul 2, 2025
04e9a79
[feat] Update GraphCanvas with VueNodeData typing
christian-byrne Jul 2, 2025
222a52d
[feat] Add feature flags and utility updates
christian-byrne Jul 2, 2025
124db59
[feat] Implement callback-driven widget updates
christian-byrne Jul 3, 2025
cd3296f
[feat] Add viewport debug overlay for TransformPane
christian-byrne Jul 3, 2025
122170f
[fix] Fix widget value synchronization between Vue and LiteGraph
christian-byrne Jul 4, 2025
0de3b8a
[feat] Add QuadTree spatial data structure for node indexing
christian-byrne Jul 4, 2025
a23d8be
[feat] Add Vue-based node rendering system with widget support
christian-byrne Jul 5, 2025
c3023e4
[fix] Remove FPS tracking to prevent memory leaks
christian-byrne Jul 5, 2025
cdd940e
[fix] Add proper cleanup for nodeManager to prevent memory leaks
christian-byrne Jul 5, 2025
95ab702
[test] Add test helper utilities for Vue node system testing
christian-byrne Jul 5, 2025
a58a354
[test] Add comprehensive tests for transform and spatial composables
christian-byrne Jul 5, 2025
32ddf72
[test] Add TransformPane component tests
christian-byrne Jul 5, 2025
c246326
[test] Add performance tests for transform operations
christian-byrne Jul 5, 2025
71c3c72
[feat] Implement LOD (Level of Detail) system for Vue nodes
christian-byrne Jul 5, 2025
290906e
[refactor] Improve type safety across Vue node widget system
christian-byrne Jul 5, 2025
5cb9ba1
[feat] Add CSS LOD classes for Vue node rendering optimization
christian-byrne Jul 5, 2025
d6315a1
[feat] Add debug type definitions for spatial indexing system
christian-byrne Jul 5, 2025
7d7dc09
[perf] Optimize TransformPane interaction tracking for better perform…
christian-byrne Jul 5, 2025
d29ce21
[refactor] Remove unused variables in GraphCanvas to fix TypeScript w…
christian-byrne Jul 5, 2025
57b09da
[test] Add missing useWidgetValue import in test file
christian-byrne Jul 5, 2025
18854d7
[cleanup] Remove temporary documentation and planning files
christian-byrne Jul 5, 2025
555e806
[perf] Optimize widget rendering performance
christian-byrne Jul 6, 2025
9a93764
[refactor] Extract canvas transform sync to dedicated composables
christian-byrne Jul 6, 2025
4304bb3
[test] Relocate and update test files
christian-byrne Jul 6, 2025
30728c1
[cleanup] Remove unused viewport culling settings and variables
christian-byrne Jul 6, 2025
32c8d0c
[refactor] Move QuadTreeBenchmark to test directory
christian-byrne Jul 6, 2025
1098d3b
[feat] Enhanced LOD system with component-driven approach (#4371)
christian-byrne Jul 7, 2025
108e54c
[perf] Global CSS optimizations for LOD system (#4379)
christian-byrne Jul 8, 2025
c6422da
Vue Node Body (#4387)
benceruleanlu Aug 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
58 changes: 0 additions & 58 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,58 +0,0 @@
- use `npm run` to see what commands are available
- For component communication, prefer Vue's event-based pattern (emit/@event-name) for state changes and notifications; use defineExpose with refs only for imperative operations that need direct control (like form.validate(), modal.open(), or editor.focus()); events promote loose coupling and are better for reusable components, while exposed methods are acceptable for tightly-coupled component pairs or when wrapping third-party libraries that require imperative APIs
- After making code changes, follow this general process: (1) Create unit tests, component tests, browser tests (if appropriate for each), (2) run unit tests, component tests, and browser tests until passing, (3) run typecheck, lint, format (with prettier) -- you can use `npm run` command to see the scripts available, (4) check if any READMEs (including nested) or documentation needs to be updated, (5) Decide whether the changes are worth adding new content to the external documentation for (or would requires changes to the external documentation) at https://docs.comfy.org, then present your suggestion
- When referencing PrimeVue, you can get all the docs here: https://primevue.org. Do this instead of making up or inferring names of Components
- When trying to set tailwind classes for dark theme, use "dark-theme:" prefix rather than "dark:"
- Never add lines to PR descriptions or commit messages that say "Generated with Claude Code"
- When making PR names and commit messages, if you are going to add a prefix like "docs:", "feat:", "bugfix:", use square brackets around the prefix term and do not use a colon (e.g., should be "[docs]" rather than "docs:").
- When I reference GitHub Repos related to Comfy-Org, you should proactively fetch or read the associated information in the repo. To do so, you should exhaust all options: (1) Check if we have a local copy of the repo, (2) Use the GitHub API to fetch the information; you may want to do this IN ADDITION to the other options, especially for reading specific branches/PRs/comments/reviews/metadata, and (3) curl the GitHub website and parse the html or json responses
- For information about ComfyUI, ComfyUI_frontend, or ComfyUI-Manager, you can web search or download these wikis: https://deepwiki.com/Comfy-Org/ComfyUI-Manager, https://deepwiki.com/Comfy-Org/ComfyUI_frontend/1-overview, https://deepwiki.com/comfyanonymous/ComfyUI/2-core-architecture
- If a question/project is related to Comfy-Org, Comfy, or ComfyUI ecosystem, you should proactively use the Comfy docs to answer the question. The docs may be referenced with URLs like https://docs.comfy.org
- When operating inside a repo, check for README files at key locations in the repo detailing info about the contents of that folder. E.g., top-level key folders like tests-ui, browser_tests, composables, extensions/core, stores, services often have their own README.md files. When writing code, make sure to frequently reference these README files to understand the overall architecture and design of the project. Pay close attention to the snippets to learn particular patterns that seem to be there for a reason, as you should emulate those.
- Prefer running single tests, and not the whole test suite, for performance
- If using a lesser known or complex CLI tool, run the --help to see the documentation before deciding what to run, even if just for double-checking or verifying things.
- IMPORTANT: the most important goal when writing code is to create clean, best-practices, sustainable, and scalable public APIs and interfaces. Our app is used by thousands of users and we have thousands of mods/extensions that are constantly changing and updating; and we are also always updating. That's why it is IMPORTANT that we design systems and write code that follows practices of domain-driven design, object-oriented design, and design patterns (such that you can assure stability while allowing for all components around you to change and evolve). We ABSOLUTELY prioritize clean APIs and public interfaces that clearly define and restrict how/what the mods/extensions can access.
- If any of these technologies are referenced, you can proactively read their docs at these locations: https://primevue.org/theming, https://primevue.org/forms/, https://www.electronjs.org/docs/latest/api/browser-window, https://vitest.dev/guide/browser/, https://atlassian.design/components/pragmatic-drag-and-drop/core-package/drop-targets/, https://playwright.dev/docs/api/class-test, https://playwright.dev/docs/api/class-electron, https://www.algolia.com/doc/api-reference/rest-api/, https://pyav.org/docs/develop/cookbook/basics.html
- IMPORTANT: Never add Co-Authored by Claude or any reference to Claude or Claude Code in commit messages, PR descriptions, titles, or any documentation whatsoever
- The npm script to type check is called "typecheck" NOT "type check"
- Use the Vue 3 Composition API instead of the Options API when writing Vue components. An exception is when overriding or extending a PrimeVue component for compatibility, you may use the Options API.
- when we are solving an issue we know the link/number for, we should add "Fixes #n" (where n is the issue number) to the PR description.
- Never write css if you can accomplish the same thing with tailwind utility classes
- Utilize ref and reactive for reactive state
- Implement computed properties with computed()
- Use watch and watchEffect for side effects
- Implement lifecycle hooks with onMounted, onUpdated, etc.
- Utilize provide/inject for dependency injection
- Use vue 3.5 style of default prop declaration. Do not define a `props` variable; instead, destructure props. Since vue 3.5, destructuring props does not strip them of reactivity.
- Use Tailwind CSS for styling
- Leverage VueUse functions for performance-enhancing styles
- Use lodash for utility functions
- Implement proper props and emits definitions
- Utilize Vue 3's Teleport component when needed
- Use Suspense for async components
- Implement proper error handling
- Follow Vue 3 style guide and naming conventions
- IMPORTANT: Use vue-i18n for ALL user-facing strings - no hard-coded text in services/utilities. Place new translation entries in src/locales/en/main.json
- Avoid using `@ts-expect-error` to work around type issues. We needed to employ it to migrate to TypeScript, but it should not be viewed as an accepted practice or standard.
- DO NOT use deprecated PrimeVue components. Use these replacements instead:
* `Dropdown` → Use `Select` (import from 'primevue/select')
* `OverlayPanel` → Use `Popover` (import from 'primevue/popover')
* `Calendar` → Use `DatePicker` (import from 'primevue/datepicker')
* `InputSwitch` → Use `ToggleSwitch` (import from 'primevue/toggleswitch')
* `Sidebar` → Use `Drawer` (import from 'primevue/drawer')
* `Chips` → Use `AutoComplete` with multiple enabled and typeahead disabled
* `TabMenu` → Use `Tabs` without panels
* `Steps` → Use `Stepper` without panels
* `InlineMessage` → Use `Message` component
* Use `api.apiURL()` for all backend API calls and routes
- Actual API endpoints like /prompt, /queue, /view, etc.
- Image previews: `api.apiURL('/view?...')`
- Any backend-generated content or dynamic routes
* Use `api.fileURL()` for static files served from the public folder:
- Templates: `api.fileURL('/templates/default.json')`
- Extensions: `api.fileURL(extensionPath)` for loading JS modules
- Any static assets that exist in the public directory
- When implementing code that outputs raw HTML (e.g., using v-html directive), always ensure dynamic content has been properly sanitized with DOMPurify or validated through trusted sources. Prefer Vue templates over v-html when possible.
- For any async operations (API calls, timers, etc), implement cleanup/cancellation in component unmount to prevent memory leaks
- Extract complex template conditionals into separate components or computed properties
- Error messages should be actionable and user-friendly (e.g., "Failed to load data. Please refresh the page." instead of "Unknown error")
131 changes: 131 additions & 0 deletions browser_tests/fixtures/utils/vueNodeFixtures.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import type { Locator, Page } from '@playwright/test'

import type { NodeReference } from './litegraphUtils'

/**
* VueNodeFixture provides Vue-specific testing utilities for interacting with
* Vue node components. It bridges the gap between litegraph node references
* and Vue UI components.
*/
export class VueNodeFixture {
constructor(
private readonly nodeRef: NodeReference,
private readonly page: Page
) {}

/**
* Get the node's header element using data-testid
*/
async getHeader(): Promise<Locator> {
const nodeId = this.nodeRef.id
return this.page.locator(`[data-testid="node-header-${nodeId}"]`)
}

/**
* Get the node's title element
*/
async getTitleElement(): Promise<Locator> {
const header = await this.getHeader()
return header.locator('[data-testid="node-title"]')
}

/**
* Get the current title text
*/
async getTitle(): Promise<string> {
const titleElement = await this.getTitleElement()
return (await titleElement.textContent()) || ''
}

/**
* Set a new title by double-clicking and entering text
*/
async setTitle(newTitle: string): Promise<void> {
const titleElement = await this.getTitleElement()
await titleElement.dblclick()

const input = (await this.getHeader()).locator(
'[data-testid="node-title-input"]'
)
await input.fill(newTitle)
await input.press('Enter')
}

/**
* Cancel title editing
*/
async cancelTitleEdit(): Promise<void> {
const titleElement = await this.getTitleElement()
await titleElement.dblclick()

const input = (await this.getHeader()).locator(
'[data-testid="node-title-input"]'
)
await input.press('Escape')
}

/**
* Check if the title is currently being edited
*/
async isEditingTitle(): Promise<boolean> {
const header = await this.getHeader()
const input = header.locator('[data-testid="node-title-input"]')
return await input.isVisible()
}

/**
* Get the collapse/expand button
*/
async getCollapseButton(): Promise<Locator> {
const header = await this.getHeader()
return header.locator('[data-testid="node-collapse-button"]')
}

/**
* Toggle the node's collapsed state
*/
async toggleCollapse(): Promise<void> {
const button = await this.getCollapseButton()
await button.click()
}

/**
* Get the collapse icon element
*/
async getCollapseIcon(): Promise<Locator> {
const button = await this.getCollapseButton()
return button.locator('i')
}

/**
* Get the collapse icon's CSS classes
*/
async getCollapseIconClass(): Promise<string> {
const icon = await this.getCollapseIcon()
return (await icon.getAttribute('class')) || ''
}

/**
* Check if the collapse button is visible
*/
async isCollapseButtonVisible(): Promise<boolean> {
const button = await this.getCollapseButton()
return await button.isVisible()
}

/**
* Get the node's body/content element
*/
async getBody(): Promise<Locator> {
const nodeId = this.nodeRef.id
return this.page.locator(`[data-testid="node-body-${nodeId}"]`)
}

/**
* Check if the node body is visible (not collapsed)
*/
async isBodyVisible(): Promise<boolean> {
const body = await this.getBody()
return await body.isVisible()
}
}
138 changes: 138 additions & 0 deletions browser_tests/tests/vueNodes/NodeHeader.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import {
comfyExpect as expect,
comfyPageFixture as test
} from '../../fixtures/ComfyPage'
import { VueNodeFixture } from '../../fixtures/utils/vueNodeFixtures'

test.describe('NodeHeader', () => {
test.beforeEach(async ({ comfyPage }) => {
await comfyPage.setSetting('Comfy.UseNewMenu', 'Enabled')
await comfyPage.setSetting('Comfy.Graph.CanvasMenu', false)
await comfyPage.setSetting('Comfy.EnableTooltips', true)
await comfyPage.setup()
// Load single SaveImage node workflow (positioned below menu bar)
await comfyPage.loadWorkflow('single_save_image_node')
})

test('displays node title', async ({ comfyPage }) => {
// Get the single SaveImage node from the workflow
const nodes = await comfyPage.getNodeRefsByType('SaveImage')
expect(nodes.length).toBeGreaterThanOrEqual(1)

const node = nodes[0]
const vueNode = new VueNodeFixture(node, comfyPage.page)

const title = await vueNode.getTitle()
expect(title).toBe('Save Image')

// Verify title is visible in the header
const header = await vueNode.getHeader()
await expect(header).toContainText('Save Image')
})

test('allows title renaming', async ({ comfyPage }) => {
// Get the single SaveImage node from the workflow
const nodes = await comfyPage.getNodeRefsByType('SaveImage')
const node = nodes[0]
const vueNode = new VueNodeFixture(node, comfyPage.page)

// Test renaming with Enter
await vueNode.setTitle('My Custom Sampler')
const newTitle = await vueNode.getTitle()
expect(newTitle).toBe('My Custom Sampler')

// Verify the title is displayed
const header = await vueNode.getHeader()
await expect(header).toContainText('My Custom Sampler')

// Test cancel with Escape
const titleElement = await vueNode.getTitleElement()
await titleElement.dblclick()
await comfyPage.nextFrame()

// Type a different value but cancel
const input = (await vueNode.getHeader()).locator(
'[data-testid="node-title-input"]'
)
await input.fill('This Should Be Cancelled')
await input.press('Escape')
await comfyPage.nextFrame()

// Title should remain as the previously saved value
const titleAfterCancel = await vueNode.getTitle()
expect(titleAfterCancel).toBe('My Custom Sampler')
})

test('handles node collapsing', async ({ comfyPage }) => {
// Get the single SaveImage node from the workflow
const nodes = await comfyPage.getNodeRefsByType('SaveImage')
const node = nodes[0]
const vueNode = new VueNodeFixture(node, comfyPage.page)

// Initially should not be collapsed
expect(await node.isCollapsed()).toBe(false)
const body = await vueNode.getBody()
await expect(body).toBeVisible()

// Collapse the node
await vueNode.toggleCollapse()
expect(await node.isCollapsed()).toBe(true)

// Verify node content is hidden
const collapsedSize = await node.getSize()
await expect(body).not.toBeVisible()

// Expand again
await vueNode.toggleCollapse()
expect(await node.isCollapsed()).toBe(false)
await expect(body).toBeVisible()

// Size should be restored
const expandedSize = await node.getSize()
expect(expandedSize.height).toBeGreaterThanOrEqual(collapsedSize.height)
})

test('shows collapse/expand icon state', async ({ comfyPage }) => {
// Get the single SaveImage node from the workflow
const nodes = await comfyPage.getNodeRefsByType('SaveImage')
const node = nodes[0]
const vueNode = new VueNodeFixture(node, comfyPage.page)

// Check initial expanded state icon
let iconClass = await vueNode.getCollapseIconClass()
expect(iconClass).toContain('pi-chevron-down')

// Collapse and check icon
await vueNode.toggleCollapse()
iconClass = await vueNode.getCollapseIconClass()
expect(iconClass).toContain('pi-chevron-right')

// Expand and check icon
await vueNode.toggleCollapse()
iconClass = await vueNode.getCollapseIconClass()
expect(iconClass).toContain('pi-chevron-down')
})

test('preserves title when collapsing/expanding', async ({ comfyPage }) => {
// Get the single SaveImage node from the workflow
const nodes = await comfyPage.getNodeRefsByType('SaveImage')
const node = nodes[0]
const vueNode = new VueNodeFixture(node, comfyPage.page)

// Set custom title
await vueNode.setTitle('Test Sampler')
expect(await vueNode.getTitle()).toBe('Test Sampler')

// Collapse
await vueNode.toggleCollapse()
expect(await vueNode.getTitle()).toBe('Test Sampler')

// Expand
await vueNode.toggleCollapse()
expect(await vueNode.getTitle()).toBe('Test Sampler')

// Verify title is still displayed
const header = await vueNode.getHeader()
await expect(header).toContainText('Test Sampler')
})
})
3 changes: 2 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export default [
ignores: [
'src/scripts/*',
'src/extensions/core/*',
'src/types/vue-shim.d.ts'
'src/types/vue-shim.d.ts',
'src/lib/litegraph/**/*'
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion lint-staged.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export default {

'./**/*.{ts,tsx,vue,mts}': (stagedFiles) => [
...formatAndEslint(stagedFiles),
'vue-tsc --noEmit'
'npm run typecheck'
]
}

Expand Down
Loading