6.2 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
ComfyUI custom node extension that organizes nodes. Frontend-only extension (no Python backend nodes) that uses TypeScript/Vite for the UI.
Commands
pnpm install # install deps
pnpm dev # dev server
pnpm build # typecheck + build to dist/
pnpm watch # build with watch mode
pnpm typecheck # tsc --noEmit
pnpm test # run tests
pnpm test:watch # run tests in watch mode
pnpm test:coverage # run tests with coverage report
Version bumping: uv run bump-my-version bump patch|minor|major
Architecture
Dual-stack extension: Python entrypoint + TypeScript frontend
__init__.py- ComfyUI entrypoint, pointsWEB_DIRECTORYto./distsrc/index.ts- Extension entry, registers viaapp.registerExtension()src/debug.ts- Debug logging utilities (separated for testability)dist/- Built JS output, loaded by ComfyUI frontend
Build system: Vite with custom plugin that injects import { app } from "/scripts/app.js" at build time. ComfyUI scripts are external (not bundled).
Types: @comfyorg/comfyui-frontend-types provides ComfyApp interface. Global app declared for runtime access.
ComfyUI Extension Pattern
Extensions register hooks via app.registerExtension({ name, setup?, ... }). The app object provides access to:
- Graph/canvas manipulation
- Node lifecycle hooks
- UI customization APIs
Use docs-mcp server for ComfyUI API reference.
Layout Algorithm
Hybrid Sugiyama + Bin Packing in src/layout/:
node-classifier.ts- Classify nodes (connected/disconnected/reroutes)reroute-collapse.ts- Collapse reroute chains into virtual edgesgraph-builder.ts- Build DAG from connected nodes onlylayer-assign.ts- Longest-path layer assignmentordering.ts- Size-aware barycenter crossing minimizationbin-pack.ts- FFDH algorithm for multi-column packingpositioning.ts- Assign coordinates, place disconnected nodes, restore reroutesgroups.ts- Resize groups to fit memberslayout-utils.ts- Shared functions for internal edge detection and member layer assignmentselected-groups.ts- Organize only selected groups independentlytitle-tokens.ts- Parse group title tokens and arrange nodes by mode
Configuration in types.ts:
maxColumns: 0=auto, 1=vertical stack, 2+=fixed columnscollapseReroutes: true to collapse reroute chainsdisconnectedGap: gap between disconnected zone and DAG
Key design decisions:
compactVerticallyexcludes groups from max height (prevents Y explosion)positionGroupContentsuses translation (preserves relative positions)- Group dimensions are layer-aware: width = sum of layer widths, height = max of layer heights (for groups with internal connections)
resolveGroupOverlapsdetects and shifts overlapping groups using minimal direction (up/down/right)assignNodeYPositionstracks reserved Y regions to prevent node/group overlap within layers- Idempotency: Layout is a pure function of topology + sizes; overlap resolution decoupled from node.pos writes
Selected Groups Layout
layoutSelectedGroups(graph, selectedGroupIds, config?) in src/layout/selected-groups.ts:
Context menu items (in src/index.ts):
- "Organize Group" - when single group selected
- "Organize N Groups" - when multiple groups selected
Behavior:
- Uses internal connections only (ignores external edges)
- Disconnected nodes within group stacked vertically
- Nested groups auto-included when parent selected
- External overlaps acceptable (no resolution with non-selected nodes)
- Groups resized to fit organized contents
Selection detection:
- Uses
app.canvas.selectedItems(Set of Positionable) - Duck-typing via
isLGraphGroup()checks for pos/size/title/id - Handles Float64Array (ComfyUI runtime uses typed arrays for pos/size)
Group Title Tokens
parseLayoutToken(title) and arrangeByMode() in src/layout/title-tokens.ts:
Supported tokens (case-insensitive):
[HORIZONTAL]- Single horizontal row[VERTICAL]- Single vertical column[1-9ROW]- Distribute into N rows (round-robin)[1-9COL]- Distribute into N columns (round-robin)
Behavior:
- Tokens override default layout for direct group members
- Ignores DAG structure when token present (treats all as disconnected)
[1ROW]=[HORIZONTAL],[1COL]=[VERTICAL]- Nested groups each respect their own tokens
- Groups without tokens use default behavior
- Nodes sorted by original position (left-to-right, top-to-bottom) to preserve user intent
Integration points:
selected-groups.ts:layoutGroupContents()- Token layout for "Organize Group"positioning.ts:positionGroupContents()- Token layout for "Organize Workflow"- Both handle nested groups: process bottom-up first, then include as items in parent's token layout
Testing
Vitest-based regression testing in tests/:
tests/fixtures/- 4 workflow JSONs (nested-groups, complex-parallel, simple-dag, nested-wrapper)tests/helpers.ts- Fixture loader, workflow→LGraph converter, cloneGraphtests/invariants.ts- 6 reusable assertions:assertNoOverlaps- AABB collision detectionassertNodesInsideGroups- nodes stay in groupsassertFiniteCoordinates- no NaN/InfinityassertTopologicalOrder- DAG order preservedassertIdempotent- stable after multiple runsassertGroupMembershipPreserved- nodes originally in groups remain in groups
tests/integration/layout.test.ts- Main regression teststests/integration/selected-groups.test.ts- Selected groups layout tests (34 cases)tests/integration/token-layout.test.ts- Token-based layout regression tests (8 cases)
Coverage: 70% line threshold, excludes src/index.ts (ComfyUI runtime) and src/layout/reroute-collapse.ts (no fixtures)
All fixtures pass all invariants including idempotency. 75 total tests.
CI/CD
.github/workflows/publish_action.yaml - Manual dispatch workflow that:
- Runs tests
- Bumps version via bump-my-version
- Creates GitHub release
- Builds and publishes to Comfy registry