6.7 KiB
6.7 KiB
Migration Plan: Comfy-Org/ComfyUI-React-Extension-Template
Repo is being updated to match the Comfy-Org/ComfyUI-React-Extension-Template pattern,
adding a React/TypeScript Style Mixer UI alongside the existing custom nodes.
Steps
✅ Step 0 — Save this plan (first order of business)
✅ Step 1 — Clean up the Python layer
- Save plan to
PLAN.md - Delete broken tests (
tests/test_immac_tools.py— asserts old-style class attributes that don't exist onio.ComfyNode) - Remove dead dict registration from
src/immac_tools/__init__.py(NODE_CLASS_MAPPINGS,NODE_DISPLAY_NAME_MAPPINGS,WEB_DIRECTORY) - Remove unused
ExampleForwardingExtensionfromsrc/immac_tools/forwarding_nodes.py
✅ Step 2 — Rewrite root __init__.py to match the template pattern
- Restore
NODE_CLASS_MAPPINGS+NODE_DISPLAY_NAME_MAPPINGS(required by ComfyUI core and Manager;comfy_entrypointalone doesn't surface nodes universally yet) - Wire
comfy_entrypointtosrc/immac_tools/nodes.py(forward-compat) - Register aiohttp static routes to serve
dist/immac_style_mixer/at/immac_style_mixer/ - Register
nodes.EXTENSION_WEB_DIRS[project_name]usingcomfy_config(with fallback)
✅ Step 3 — Add Style Mixer REST API (src/immac_tools/api.py)
style_mixer_data.jsonload/save helpersGET /immac_style_mixer/api/dataPOST /immac_style_mixer/api/data(with basic structural validation)- Routes wired into root
__init__.pyviaregister_routes() - Images handled by ComfyUI built-ins (
/upload/image+/view)
✅ Step 4 — Update pyproject.toml
- Change
includes = []→includes = ["dist/"] - Remove
[build-system]/[tool.setuptools]blocks
✅ Step 5 — Scaffold ui/ (React + TypeScript + Vite)
ui/package.jsonui/vite.config.ts— output →../dist/immac_style_mixer/ui/tsconfig.json+ui/tsconfig.node.jsonui/src/main.tsx— registers ComfyUI sidebar tabimmac-style-mixerui/src/components/StyleMixerPanel.tsx— placeholder- Build verified (
npm run build✔)
✅ Step 6 — Build UI components
types.ts—Style,Mix,MixEntry,StyleMixerDatahooks/useStyleMixerData.ts— fetch/save via API;uploadStyleImage()+styleImageUrl()helperscomponents/StyleCard.tsx— image upload (drag&drop/picker), editable name & prompt, favorite star, deletecomponents/MixCard.tsx— name, radio to activate, style entries (ON/OFF + weight + remove), add-style dropdown, favorite starcomponents/StyleGallery.tsx— sorted grid (favorites first), inline "Add" formcomponents/StyleMixerPanel.tsx— Current Mix / Mixes / Styles sections wired to data hook- Build verified (
tsc && vite build✔)
✅ Step 7 — Documentation and license
- Rewrite
README.mdto accurately describe the Style Mixer (nodes, sidebar, dev build) - Update
PLAN.mdto reflect completed features - Verify
LICENSE(MIT, 2026)
Post-plan features (completed)
These features were implemented after the core plan was done:
UI polish
- Auto-fill CSS grid layout — styles:
minmax(180px, 1fr), mixes:minmax(280px, 1fr) - Click card to select mix (removed radio button)
- Magnify button on style thumbnail hover;
scale-105CSS transition - Full-size image lightbox (
ImageLightbox.tsx) - Copy prompt button on style and mix cards (clipboard API)
- Draggable bar input for style weights (
BarInput.tsx) — matches ComfyUI slider feel - Delete confirmation shown as centered card overlay (
overflow: hiddenclipping) - Overlay shows actual style/mix name in delete prompt
Canvas integration
- Drag a style card onto the canvas → creates a
StylePickImmacStyleMixernode set to that style (usesonDragEndpattern; ComfyUI intercepts thedropevent itself) - Style Mix node shows a thumbnail preview of the selected mix's cover image (or first enabled style fallback)
- DOM widget (
addDOMWidget) renders immediately on node creation — not subject to deprecatednode.imgs/setSizeForImage mixWidget.callbackwrapping is the correct hook (fires on arrows, context menu, programmatic calls)scheduleCanvasDirty: 10 rAF frames after node creation to force the canvas to redraw and show the preview on page reload
- DOM widget (
Key Decisions
- Single
POST /immac_style_mixer/api/dataendpoint for all persistence (minimal backend; expand later) - Sidebar tab (not floating window) for the panel
dist/tracked in git — required for registry publishing viaincludes = ["dist/"]- Styles are user-defined strings (prompt text snippets)
Dev Notes
Style Mix node — preview image on widget change
Goal: show the selected mix's cover image (or a style fallback) in the node body when the mix combo widget changes, without triggering execution.
What doesn't work in the current ComfyUI frontend:
node.imgs+setSizeForImage()— deprecated. Settingnode.imgshas no visible effect unless a$$canvas-image-previewDOM widget is already present on the node, which only happens after the node executes at least once (viaonExecuted).setSizeForImageis explicitly@deprecatedin the frontend's type definitions.Object.definePropertyonwidget.value— theComboWidgetclass stores its value in a private field. Shadowing.valueon the instance intercepts external assignments but misses internal stepped-widget logic that writes to the private field directly (e.g. arrow button navigation).
What works:
node.addDOMWidget(...)injects a real<img>HTML element into the node body. It renders immediately on node creation, is fully controlled by us, and isn't subject to any of the deprecation or class-internals issues.mixWidget.callbackwrapping is the correct hook for value changes — ComfyUI calls it reliably on all interaction paths (arrows, context menu, programmaticwidget.callback?.(value)calls).widget.valueis already updated by the time callback fires.
Implementation (ui/src/main.tsx, nodeCreated hook):
- Create a
<div><img></div>DOM widget on the node (serialize: false). - Wrap
mixWidget.callbackto callupdatePreview(mixWidget.value). updatePreviewfetches/immac_style_mixer/api/data, finds the mix, resolves the image URL (mix cover → first enabled style thumbnail → nothing), and setsimgEl.src. The<img>is hidden until loaded.scheduleCanvasDirtyfiressetDirtyCanvas(true, true)across 10 rAF frames after node creation so the preview appears on page reload without a hard race condition against the LiteGraph render loop.