Skip to content

Releases: Deaththegrim/ComfyUI-PromptLibrary

v0.55.2 — hand-pass VRAM ceiling + Queue ▶▶ per-gallery

08 May 07:15

Choose a tag to compare

Detailer

  • Hands preset crop_factor 2.0 → 1.5. At hires the hand bbox already has plenty of pixels; the 2x context made the upscaled crop near-fullres and UNet sample + VAE on that latent was the highest VRAM peak in detail(). Was close to locking up 16 GB cards even with v0.55.0's diet. Per-target override still works.
  • Tiled VAE encode/decode threshold 1024 → 768. More crops go tiled instead of risking the non-tiled peak. Tiled retry already starts at tile=256, so the cost is bounded.

Library UI

  • Queue ▶▶ drives THIS gallery's parent node instead of the first PromptLibrary in the graph. Multi panel can now cycle its own panel (others fixed); Style node can cycle styles. Widget values are saved/restored around the loop so multi-select stays in sync after the run.
  • Multi-select persistence fix: skip the 'prune missing IDs' pass when prompts is empty during initial onConfigure render — without the guard, a freshly-restored selection got wiped on first render.

v0.55.0 — VRAM diet + lockup mitigation

08 May 06:36

Choose a tag to compare

What's new

Detailer and sampler hardening targeting an AMD/HIP hard-freeze fingerprint observed under multi-pass detailer runs (gfxhub page faults → VRAM exhaustion → wedge).

Smart Detailer

  • Per-bbox soft_empty_cache removed — flushing the allocator every iteration defeated pooling and forced HIP alloc/free roundtrips (the wedge driver). Per-pass flush retained.
  • fp32→fp16 cast hoisted to detail() so subsequent passes reuse the owned buffer instead of cloning per-pass (saves ~100-200 MB at hires resolution per pass).
  • sam_mask_cache pruned between passes — drops bboxes no future pass will reuse. A 6-pass disjoint-detection run no longer holds full-resolution mask tensors for the whole call.
  • SAM predictor offloads to CPU at end of detail(), reclaiming ~600 MB-1.5 GB VRAM between runs without invalidating the model cache.

Samplers (SDXL + Anima)

  • Tile-step-down OOM retry for pixel-upscale-with-model and VAE encode/decode (256 → 128 → 64) instead of crashing.
  • sampler_sdxl frees decoded and upscaled between hires iterations (~200 MB of dead weight per iteration).
  • sampler_anima defragments allocator between sample and decode under fragmentation pressure.

Tests

+9 unit tests covering the new offload helper and the cache-pruning algorithm. 333 pass, 5 skip, 1 deselected (pre-existing unrelated comic_page test).

v0.53.1 — Smart Detailer mouth/feet targets + widget reorder

07 May 06:31

Choose a tag to compare

v0.53.1 — Smart Detailer mouth + feet targets, library persistence

Smart Detailer

Two new targets

Target Default crop Wildcard
mouth/teeth 2.5 "detailed mouth, clean teeth, sharp lips,"
feet/shoes 2.5 "detailed feet, sharp shoe details, clean stitching,"

Pass order is now face → skin → mouth → eyes → feet → hands — mouth runs after skin so the freshly-detailed face is the input for teeth/lip work, eyes run after mouth so the iris detail lands on the cleaned-up face.

The per-target grid UI grew from 4 to 6 columns. Default node width bumped to 480 so the grid fits.

Widget order rationalised

Required widget order is now logically grouped:

enable_face, enable_eyes, enable_mouth, enable_hands, enable_feet, enable_skin
bbox_face,   bbox_eyes,   bbox_mouth,   bbox_hands,   bbox_feet,   sam_model
seed, steps, cfg, sampler_name, scheduler, denoise, ...

This is a breaking widget-shift relative to v0.53.0. Two fixes:

  • Run python3 tools/fix_workflow_widgets.py <workflow.json> -o to migrate saved workflows in place (with .bak)
  • Or right-click the Smart Detailer node → Remove → re-add fresh

The migration tool's widget order list was updated to match.

Quality-default sweep

Already shipped at v0.52.1 but worth restating since these defaults travel with the node:

Widget New default Was
steps 25 20
denoise 0.45 0.40
guide_size 1024 512
max_size 1536 1024
bbox_threshold 0.45 0.5
yolo_imgsz 960 640

Library

Selection now persists across page refreshes via belt-and-suspenders:

  • Primary store: idWidget.value (comma-joined ids, ComfyUI auto-serialises)
  • Backup mirror: node.properties[propsKey].selectedIds (always serialised regardless of widget-value handling)
  • syncFromWidget falls back to the properties mirror when the widget value comes back empty on restore

applySelectionChange now calls writeState on every selection mutation so the mirror is always fresh.

Recommended detector models

Drop into models/ultralytics/bbox/:

  • face: Anzhc Face seg 640 v4 y11n.pt (HF: Anzhc/Anzhcs_YOLOs) — YOLO11n, stylised-friendly
  • eyes: Anzhc Eyes -seg-hd.pt (HF: Anzhc/Anzhcs_YOLOs) — anime sclera-tuned
  • mouth: Civitai 1306938 v1.0 — YOLO11n illustration mouth
  • hands: Civitai 329458 v9c — YOLOv9c, catches partial/off-angle hands the v8s version misses
  • feet: Civitai 2511165 v0.4-seg — YOLO11n foot+shoe segmentation

v0.52.1 — Smart Detailer per-target grid UI + quality defaults

07 May 05:27

Choose a tag to compare

v0.52.1 — Smart Detailer: per-target grid UI + quality defaults

UI

Replaces the long vertical stack of 16+4 per-target widgets (enable / threshold / denoise / max / steps × face / eyes / hands / skin) with a compact 5-column grid:

              FACE      EYES     HANDS    SKIN
enable        [☐]       [☐]      [☐]      [☐]
threshold    [-1.00]   [-1.00]  [-1.00]  [-1.00]
denoise      [-1.00]   [-1.00]  [-1.00]  [-1.00]
max N        [   0]    [   0]   [   0]   [   0]
steps        [   0]    [   0]   [   0]   [   0]
  • Column headers colour-coded to match the bbox preview: face=green, skin=yellow, eyes=cyan, hands=magenta.
  • "Use global" sentinel values (-1 for floats, 0 for ints) render dimmed-italic so overrides are visually obvious.
  • Underlying widgets stay in node.widgets for serialisation — hidden behind the DOM panel via the same pattern Multi Library uses. Workflows saved against v0.52.0 load fine.
  • Default node width bumped to 380 so the grid fits cleanly.

Quality-optimum defaults

Widget Old New Why
steps 20 25 cleaner sampling
denoise 0.40 0.45 enough detail without identity drift
guide_size 512 1024 SDXL-native resolution
max_size 1024 1536 room for SDXL crops to breathe
bbox_threshold 0.5 0.45 catches partial / off-angle faces
yolo_imgsz 640 960 catches background / small faces

Tooltips updated to call out "quality default" and document the speed-vs-quality knob direction for each widget.

v0.52.0 — Smart Detailer + no-pack-deps cleanup

07 May 05:12

Choose a tag to compare

v0.52.0 — Smart Detailer + no-pack-deps cleanup

What's new

🔬 GrimmRibbity Smart Detailer (new node)

Self-contained one-node face/eyes/hands/skin detailer. Replaces the 3-node Impact-Pack FaceDetailer chain with target toggles, in-node YOLO loading by filename, optional SAM mask refinement, and ComfyUI's built-in sampler. Zero ComfyUI custom-node deps — uses ultralytics + sam2 / segment_anything from PyPI directly.

Six rounds of perf optimisation:

  • Round 1 — SAM set_image hoisted to per-target, conditional tiled VAE decode (full for ≤1024px, tiled above), ProgressBar across all bboxes, single image-to-uint8 conversion shared YOLO+SAM+preview, empty-detection fast path.
  • Round 2 — module-level CLIP encode LRU (survives across calls), edge-aware feathering (no rect seam at image borders), mask_strength widget, optional tiled_encode, hot-path imports hoisted, same_seed_per_target toggle.
  • Round 3 — YOLO model.fuse() (Conv+BN, 10–20% inference speedup), explicit GPU pin, per-pass wall-time logging.
  • Round 4 — SAM2 batched-prompt predict (one forward instead of N for crowd scenes), lazy preview clone, nms_iou widget, yolo_imgsz widget.
  • Round 5 — per-target steps overrides, text-labelled bboxes on preview output, SAM mask cache shared across passes (face+skin stop double-SAMing identical bboxes).
  • Round 6 — SAM v1 batched predict via predict_torch (parity with SAM2), soft-cancel between bboxes, draw_preview toggle, max_bbox_area_pct sanity cap on whole-image false positives.

NaN-scrub on every refined-image path so downstream nodes never see NaN → uint8 cast garbage. Outputs: image, mask, detections_preview.

🧹 No-pack-deps cleanup

  • Character Anchor — IPAdapter Plus import is lazy at anchor() call time. Module loads cleanly without IPAdapter installed; node always visible in the picker.
  • SDXL Sampler — dropped efficiency-nodes city96/ttl_nn neural latent upscalers. Legacy strings retained on the dropdown so saved workflows still validate; runtime falls back to bicubic with a console warning.
  • Detailer — idempotently registers ultralytics_bbox/segm/sams folder paths so dropdowns work even without Impact Subpack installed.

🛡️ Code-scanning fixes (CodeQL)

  • py/mismatched-multiple-assignment — explicit 8-tuple in _unpack_sdxl_tuple.
  • js/xss-through-dom — added f.type.startsWith("image/") guard before URL.createObjectURL in the upload handler.
  • py/multiple-definition — removed dead pending_since variable from the watcher loop.
  • py/overly-permissive-file — prompt-log fd mode 0o6440o600 (owner-only).
  • py/unused-import × 3 — dropped unused time from civitai_save.py + tools/screenshots/run.py, Path from tests/test_civitai_save.py.

🎨 Web + theme

  • Smart Detailer (#d8754a burnt orange) and Character Anchor (#8eaa3e olive) added to NODE_COLORS.
  • Selection-state diagnostic logging in promptLibrary.js for the multi-Library ghost-select bug investigation; toggle off via window._plDebugSelect = false.

✅ Tests

299 passing (up from 263). New tests/test_detailer.py covers helpers, bypass/passthrough, INPUT_TYPES shape, edge-aware feather mask, wildcard cache LRU, conditional tiled-decode policy, font drawer.

Install

Clone or unzip into your ComfyUI custom_nodes/ directory and restart ComfyUI. The Smart Detailer node appears under right-click → Add Node → GrimmRibbity → Detailer → Smart Detailer.

Requires ultralytics (already installed via Impact Pack typically). Optional: sam2 or segment_anything for mask refinement.

v0.47.1 — HiResFix round 5 + Civitai batch lift + Wildcard IS_CHANGED

07 May 02:11

Choose a tag to compare

v0.47.1 — Round-5 HiResFix wins + cross-node perf sweep

Folded changes since v0.46.2.

v0.47.0 — HiResFix round 5

What changed Real-world impact
Soft GPU cleanup between iterations New _between_iterations_cleanup() calls comfy.model_management.soft_empty_cache() after every iteration. Gentler than torch.cuda.empty_cache() — only fires when memory is actually constrained. Long iteration loops (4–5 iters at 4× scale) used to accumulate VRAM fragmentation; iteration 5 sometimes allocated around fragments instead of into them. Stable now.
denoise=0 short-circuit common_ksampler at denoise=0 still ran its noise+step setup ritual (~1 s per call) even though no sampling happens. The call is now skipped entirely; the upscaled latent flows through unchanged. Saves ~1 s × N iterations × stages on "upscale-only" workflows. Both samplers.
Per-pass hires_sampler / hires_scheduler Two new optional widgets on the SDXL HiResFix Script. (same as primary) default = inherits the primary call (no behaviour change). Override to use a different sampler at the hires stage. Useful for "primary dpmpp_3m_sde + karras + hires euler + simple" patterns, or swapping to faster samplers when hires steps are cheap.

v0.47.1 — Cross-node perf sweep

What changed Impact
Civitai Save: lift batch-shared work out of the per-frame loop build_a1111_parameters + json.dumps(prompt) + json.dumps(extra_pnginfo) were called per frame inside the save loop. Frames in a Comfy batch always share H/W, so the parameter string is identical for every frame; same for the workflow-trace JSON. Batch of 4 with a typical trace: ~3 redundant param-string builds + 3 redundant JSON dumps avoided per save. Compounds across overnight batches.
Wildcard Expand IS_CHANGED tracks library mtime Same staleness pattern as the Random by Tag fix in v0.42.1. With expand_named_refs=True, the result depends on whichever entries __name__-references resolve to. Edits to those entries mid-session must invalidate Comfy's cached output. mtime folded into the hash only on the named-ref branch — pure {a|b|c} expansion still hashes fast. Edits to library entries now actually re-trigger downstream Wildcard Expand consumers at fixed seed.

Audit notes — verified no fix needed

Node Status
Scene / Background / Comic Frame Pure-string composition; Comfy's default input-hash IS_CHANGED is correct
Save / ThumbnailSaver OUTPUT_NODE=True, side-effect writers — should re-run every queue
Multi node IS_CHANGED already hashes picked entries' text
Library node IS_CHANGED hashes per-entry text + negative + LoRA sig (when MODEL/CLIP wired)
Style / Comic Page / Character Anchor Required-input nodes, Comfy hashes inputs by default
LoRA Picker Stateless (per-call session-cached saves)

Cumulative HiResFix optimization picture (v0.32 → v0.47.0)

16 HiResFix-specific items shipped across 5 rounds. Every per-iteration cost that can be hoisted has been; every load that can be cached is; every redundant work step short-circuits when not needed.

Backward compat

All changes internal — no socket / widget / workflow JSON shape changes since v0.46.2. Workflow JSONs from any version since the optional-MODEL/CLIP refactor (v0.32) still load cleanly.

Install / upgrade

cd ComfyUI/custom_nodes/ComfyUI-GrimmRibbity
git pull
# restart ComfyUI / hard-refresh browser tab

v0.46.2 — Sticky-prompt_id hardening + half-wired warning

07 May 01:52

Choose a tag to compare

v0.46.2 — Sticky-prompt_id hardening + Library half-wired warning

Triggered by a user report: overwrite_by_name on the Save node was "sticking" and overwriting the latest entry instead of the named one. Library scan showed zero duplicate names, so the most likely cause was a sticky prompt_id widget masking overwrite_by_name. Two patch releases folded into this tag.

v0.46.1 — overwrite_by_name + modal collision

What changed
overwrite_by_name picks most-recently-updated Was first-by-storage-order = oldest dup. Now matches "the one I last edited" intent. Collapses to the single match when no dupes.
Save node logs the lookup path Every Save now prints via=prompt_id / via=overwrite_by_name / via=new to the Comfy console. The diagnostic for sticky widget values — if every save logs via=prompt_id even when the user expected overwrite_by_name to fire, the Save node's prompt_id field has a pinned value to clear.
Modal +Add warns on duplicate name Was silently appending _2 / _3 (the actual mechanism that lets users accumulate same-named entries). Now pops a "Save as new / Cancel and edit existing" confirm.

v0.46.2 — ThumbnailSaver + Library half-wired warning

What changed
ThumbnailSaver success log PromptLibraryThumbnailSaver has the same prompt_id widget as the Save node — same risk class. Previously logged only failures; now also logs success: [ThumbnailSaver] wrote thumbnail to id='foo' name='Foo Style'.
Library half-wired MODEL/CLIP warning Wiring just MODEL or just CLIP into the Library node silently skipped the LoRA path (both required). Now prints a clear warning so the user knows why their LoRAs aren't applying.

Audit notes — no fix needed

  • Style node has MODEL + CLIP as required, so half-wired is structurally impossible.
  • Multi-panel node uses prompt_id_<N> widgets driven by the gallery, hidden, cleaned on node delete (v0.37); not at risk.
  • All other next() lookups are by id, which is unique by construction (_unique_id + _safe_id).

How to diagnose the original bug

Restart Comfy, run your Save-node workflow once, check the console:

[PromptLibrary] saved id='X' name='X' via=prompt_id        ← sticky prompt_id
[PromptLibrary] saved id='X' name='X' via=overwrite_by_name ← working as intended
[PromptLibrary] saved id='X' name='X' via=new              ← created new

If via=prompt_id when you expected overwrite_by_name, the Save node's prompt_id field has a pinned value. Clear it.

Tests

263 total (was 261, +2). The new cases cover overwrite_by_name picking the newest match and creating-when-no-match.

Backward compat

All changes internal — no socket / widget / workflow JSON shape changes.

Install / upgrade

cd ComfyUI/custom_nodes/ComfyUI-GrimmRibbity
git pull
# restart ComfyUI / hard-refresh browser tab

v0.46.0 — Gallery validator + 11 rounds of audit polish

07 May 01:33

Choose a tag to compare

v0.46.0 — Gallery-wired library validator + 11 rounds of audit polish

The cumulative changeset since v0.39 (the last tagged release). Eleven rounds of audit-driven work, 34 items shipped, 2 silent data-loss / curation bugs surfaced, 261 tests (was 242, +19), 3 new standalone CLI tools. All changes internal — no socket / widget / workflow JSON shape changes.


v0.46.0 highlights — library validator wired into the gallery

The gallery now surfaces library-health issues directly in the toolbar. A new ⚠ N badge appears only when there are unhealed issues (clean libraries get no UI clutter). Click it to open a categorised modal:

  • Broken LoRA references — entries whose loras list references a .safetensors no longer in models/loras/. Per-row Edit button opens the entry so you can re-pick or delete the row.
  • Orphan thumbnails — image files with no matching entry. Single "Remove orphan thumbnails" action calls the new POST /prompt_library/fix_orphans endpoint.
  • Invalid entry IDs — entries whose id doesn't match [A-Za-z0-9_-]{1,64} (would refuse to upsert).
  • Empty-text entries — entries with no prompt text from a half-completed save.

Two new HTTP routes:

  • GET /prompt_library/validate — read-only findings dict
  • POST /prompt_library/fix_orphans — bulk-delete stray thumbnails

The badge re-fetches after every gallery refresh so the count stays accurate. Network failures hide the badge silently — the gallery itself works whether the validator endpoint is reachable or not.

Round J — drag insertion line + library_validate CLI

  • Drag-reorder visual — the previous "outline the target tile" gave an ambiguous "replace or sit next to?" affordance. Now a 3px accent-coloured line on the leading edge of the target tile (top edge in list view) literally shows where the dragged tile will land.
  • tools/library_validate.py — the same checks as the gallery validator, runnable from the shell. Read-only by default; --fix-orphans deletes stray thumbnails.

Round I — search + modal usability

  • Gallery search now matches LoRA fields + negative + notes — used to walk only name / text / tags / id. A query like turbo (LoRA filename) or green eyes (trigger word) used to miss matching entries.
  • Visible missing-LoRA warning — modal LoRA rows whose path isn't in the loaded list now show a red banner above the dropdown, not just a (missing) marker the user has to click the dropdown to see.
  • PromptLibrarySave loras_json input — workflows can now save entries with attached LoRAs programmatically. Empty input leaves existing loras untouched; [] explicitly clears.

Earlier rounds (v0.40 → v0.45)

Round What landed
A v0.40.0 zip-bomb cap, CSV cap, /list directory cache, JSON validation, atomic prompt_log writes
B v0.40.1 SHA cache LRU, named depth constants, deep-copy loras
C v0.40.2 end-to-end roundtrip tests + fix negative field silently lost on every export → import
D v0.41.0 dual M/C strength sliders + LoRA-row soft-delete + Scene empty-output warning
E v0.42.0 Comic Page per-panel panel_strengths override + cycle test + loras-in-record test
F v0.42.1 LoRA Picker port discovery, Random IS_CHANGED tracking, watcher try/except resilience
G v0.43.0 tools/civitai_backfill.py CLI
H v0.44.0 snapshot path-traversal hardening, watcher coalesce, modal try/catch, gallery lazy-render via requestIdleCallback, strength link-toggle

Two real bugs found by the audit

  1. Negative-field round-trip leak (Round C) — every export → import lost the entry's negative prompt. Found by the new end-to-end test.
  2. Empty-text "Cyberpunk" entry (Round J/K) — surfaced by the validator running against the maintainer's live 718-entry library; would never have been noticed otherwise.

Backward compat

All changes internal — no socket / widget / workflow JSON shape changes since v0.39. Drop-in safe.

Install / upgrade

cd ComfyUI/custom_nodes/ComfyUI-GrimmRibbity
git pull
# restart ComfyUI / hard-refresh browser tab

Three standalone CLI tools (in tools/)

# Diagnose library issues (read-only by default)
python3 tools/library_validate.py [--fix-orphans]

# Backfill Civitai SHA hashes onto legacy PNGs
python3 tools/civitai_backfill.py /path/to/output [--recursive] [--dry-run]

# Capture screenshots of the live gallery (since v0.36)
python3 tools/screenshots/run.py

v0.43.0 — Audit-driven polish + Civitai backfill CLI

07 May 01:13

Choose a tag to compare

v0.43.0 — Six rounds of audit-driven polish + Civitai backfill CLI

A condensed release covering grind-rounds A → G from a systematic codebase audit. Highlights below; no socket / widget / workflow JSON shape changes since v0.41.

Round A — security + perf wins

  • Zip-bomb defence on /import_zip — sums uncompressed sizes from each ZipInfo header before reading any member; rejects expansions over 2 GB.
  • CSV size cap (50 MB) on /import_csv — was unbounded; now consistent with image (16 MB) and zip (500 MB) caps.
  • /list directory cache_image_path_for was doing ~7 stat() calls per entry inside the list response (4900 syscalls for a 700-entry library per /list). Now one os.scandir builds an id set per request.
  • JSON body validation — new _json_payload helper centralises the inconsistent pattern across 5 POST routes; malformed bodies → 400 with a clear message instead of 500. Adds missing ids must be a list check to /export and /bulk_delete.
  • Atomic prompt_log writes — single os.write(fd, line_bytes) + fsync per JSONL line. Process crash mid-write can't corrupt the file anymore.

Round B — quality / consistency

  • SHA256 hash-cache LRU capped at 4096 entries (~320 KB on disk).
  • Named depth constants (_TEXT_LINK_MAX_DEPTH, _PIPE_TRACE_MAX_DEPTH) replace bare 4 / 8 literals in the Civitai workflow walker.
  • Deep-copy loras on duplicate — was a shallow [dict(l) for l in src["loras"]].

Round C — test coverage + a real bug

  • End-to-end save → export → reimport round-trip test — caught a silent data-loss bug: the negative field was being exported but never read back on import. Fixed alongside.
  • New tests for import_backgrounds and import_tag_packs routes.
  • 247 tests, was 242.

Round D — modal UX

  • Independent CLIP-strength slider in the Edit Prompt modal — each LoRA row now has stacked M and C slider+number pairs. Backend always tracked strength_model and strength_clip separately; the UI was silently mirroring them. Pre-v0.40.3 entries with only strength_model saved → strength_clip defaults to the model value on load (mirror preserved as fallback).
  • Soft-delete on LoRA rows — Delete now toggles to "Restore"; row dims and strikes through but stays editable. Misclick is reversible without abandoning every other modal edit.
  • Scene-node empty-output warningPromptLibraryScene with every knob at (none) and empty extra used to silently emit "". Now logs.

Round E — Comic Page + more tests

  • Per-panel strength override on Comic Page (Regional) via a new optional panel_strengths CSV widget. Position N maps to panel N; blank/missing positions fall back to the global default.
  • 5 new PerPanelStrengthTests, 1 new WalkModelChainCycleTests, 1 new build_record-with-loras test.
  • 254 tests, was 247.

Round F — small misc items

  • LoRA Picker auto-detects ComfyUI's host:port from PromptServer.instance.server instead of hardcoded 127.0.0.1:8188.
  • PromptLibraryRandom.IS_CHANGED now folds the prompts.json mtime into the cache key, so editing a tag-matched entry mid-session invalidates the cached pick at fixed seed too.
  • Watcher loop wraps each iteration in try/except Exception so a future bug in _notify_change can't kill the daemon and break gallery auto-refresh for the rest of the session.

Round G — Civitai metadata backfill CLI

python3 tools/civitai_backfill.py /path/to/output --recursive --verbose

Walks a folder of PNGs, reads the workflow JSON Comfy embeds in the prompt chunk, computes SHA256 hashes for the model + LoRAs (cached on disk in the same data/hash_cache.json the live node uses), and rewrites the parameters chunk with a proper Civitai Hashes: {...} JSON block. Idempotent — already-tagged PNGs are skipped on re-run. Useful for a backlog of generations from before the Save node was wired in.

Designed to run independently of ComfyUI — no live server needed, no Python deps beyond Pillow.

Backward compat

All changes internal — no socket / widget / workflow JSON shape changes. Drop-in safe with v0.41.x and v0.42.x workflows.

Install / upgrade

cd ComfyUI/custom_nodes/ComfyUI-GrimmRibbity
git pull
# restart ComfyUI / hard-refresh browser tab

v0.39.0 — Gallery event delegation

07 May 00:40

Choose a tag to compare

v0.39.0 — Gallery event delegation

The last big perf hit on huge libraries: every tile attached its own click / contextmenu / dragstart / dragend / dragover / dragleave / drop handlers, plus a checkbox.onclick on the inner element. For a 700-tile library in Manual sort mode that created ~4900 handler closures per render, each capturing checkedIds / syncWidget / render / refresh / openPromptModal / etc. via lexical scope. Render-then-discard cycle hit GC hard on every filter or sort change.

What changed

Replaced with a single set of grid-level delegated handlers attached once per gallery instance. Each delegated handler:

  • resolves the affected tile via e.target.closest('.pl-tile')
  • reads dataset.promptId
  • looks up the entry via prompts.find / lastVisible.findIndex
  • routes to the same logic as before

Five new helpers (private to buildGallery)

_entryFromTile(tile)        → entry from prompts[]
_indexFromTile(tile)        → index in lastVisible[]
_toggleSelectionAt(id, ...) → plain + shift-range click
_openAddPrompt()            → '+' tile click handler
_openTileContextMenu(e, p)  → right-click menu builder

Per-tile creation cost

Operation Before (per tile) After (per tile)
createElement + setAttrs yes yes
Closure constructions 7–9 0
Inner-DOM build yes yes

For 700 tiles in Manual sort: ~4900 closures NOT created on render.

Cumulative gallery perf wins (v0.36 → v0.39)

Action on 700-entry library v0.36 v0.39
Type one search character full grid rebuild + 4900 closures debounced 80 ms + DocumentFragment + 0 closures
Click a tile to select full grid rebuild + 4900 closures one tile's classList toggled
Shift-range select 100 tiles full grid rebuild + 4900 closures 100 tiles' classes toggled
Arrow-key navigation full grid rebuild + 4900 closures per keystroke 2 DOM mutations per keystroke
Bulk clear selection full grid rebuild + 4900 closures N tiles' classes removed
Sort change full grid rebuild + 4900 closures full rebuild + 0 closures (correct order needed)
Right-click for context menu per-tile closure already there one delegated handler per gallery

Search-as-you-type, click-to-select, and arrow-key nav on 500–700 entry libraries should feel instant. Every interaction except a genuine reorder is now O(few-DOM-mutations) instead of O(visible-tiles).

Backward compat

All changes internal — no socket / widget / workflow JSON shape changes.

Install / upgrade

cd ComfyUI/custom_nodes/ComfyUI-GrimmRibbity
git pull
# restart ComfyUI / hard-refresh browser tab