Two bugs in state_to_configs_json's VAE emission:
1. Bypass (unchecked) VAEs were still sent through — vae_bypass_states
was never consulted in the filter. Now filtered alongside the
None/empty check.
2. When no VAE remained (empty list, all bypassed, or only "None"),
the config emitted "vae": "None" — the orchestrator interpreted
that as a model filename and threw "vae model with filename 'None'
not found". Now the "vae" key is OMITTED entirely so the generator
falls back to its default behavior (no per-config VAE override),
matching the pattern already used for text_encoders/clip_type/etc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User wanted Run Settings to be the FIRST section, above everything
including Distribution Settings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 1 of consolidating run-level config into the Builder UI. The
Builder UI's "Run Settings" section is now rendered at the TOP of the UI
(was previously below config arrays) and gains six new fields with help
tooltips:
- overwrite_existing (bool, default false)
- flush_batch_every (int, default 4)
- lora_triggerwords_mode (enum: None / Append To End / Append To Start /
Read From Config, default None)
- save_conditioning_cache_to_file (bool, default false)
- enable_model_cache (bool, default false)
- vae_batch_size (int, default 4, range -1..64)
State plumbs through state_to_configs_json -> _session_settings ->
sampler_node.run_tests -> run_generation_loop. Builder UI values
override the corresponding widgets on UltimateSamplerGrid when set
(non-default). Widget values remain the fallback so existing workflows
keep working without changes.
Logs an "⚙️ <name> overridden by Builder UI: <value>" line whenever an
override fires, so it's obvious in the console.
Next: once the user has tested this thoroughly, the corresponding
widgets on the SamplerGridTester node can be removed/hidden to
simplify the generator UX (deferred — coordinate with user).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The lightbox-click revert (4a318e6) stripped both onclick AND ondblclick
from the <img> tag. The intent was to remove the accidental-tap-opens-
lightbox behavior, but the double-click-to-favorite was collateral damage.
Videos kept their ondblclick handler; images lost it.
Restoring ondblclick="toggleFavorite(this)" on <img>. No onclick — single
click still does nothing (panning safe).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
These changes belong with the earlier "preview panel uses /configbuilder/preview"
work (b6e2344) but were dropped from that commit. Folding in now:
- Adds .cb-preview-panel + resize-handle + header/content CSS used by the
renderPreviewSection function shipped in 991631a.
- Removes the obsolete addNavIcon("📄", "JSON Preview", ...) sidebar entry.
- Cleans the cb-sec-preview ID out of the scroll-spy section list.
No behavior change beyond what was already committed — fills in the
visual/CSS gap the committed JS depends on.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
These keys legitimately aren't set in pre-orchestrator configs:
- seed: filled by expand_configs (node-level seed widget or per-entry override)
- denoise: filled by run_generation_loop from the denoise widget
- clip_type: only set when model_type != "checkpoint"
The validator was a stale leftover from the pre-single-source-of-truth era.
Trimming false positives. Still warns on actual typos / dropped fields.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User reported that changing seed in the Revise modal had no effect —
the new seed was overwritten and the orchestrator's check_if_job_completed
matched the original item's seed, skipping the gen.
Root cause: config_utils.expand_configs unconditionally wrote
"seed": <global node seed> for every config, ignoring entry["seed"]
from the configs_json. Builder UI doesn't emit entry["seed"] (it relies
on the node-level seed widget), so this regression only surfaced when
something else explicitly set per-entry seed — exactly what triggerGen
does.
Fix: use entry["seed"] when present and non-empty, otherwise fall back
to the global seed. Builder UI flow unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two QoL tweaks:
- CANCEL button text → EXIT (clearer when paired with the new "stays open"
behavior — there's nothing to "cancel" after queuing).
- Generate New no longer closes the modal. The QUEUED! flash on the button
is enough confirmation. Users can keep tweaking sampler/cfg/prompt and
queueing additional revisions of the same image without re-opening.
Close manually via EXIT or Esc.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User reported edited sampler/cfg/steps/prompt were ignored — the original
config ran and got skipped as already-existing.
Root cause: configs_json on UltimateSamplerGrid is declared as both a
widget AND an input socket. In the typical setup (UltimateConfigBuilder
wired into UltimateSamplerGrid), the input link OVERRIDES the widget at
queue time. The previous triggerGen set widget.value but Comfy used the
upstream node's output instead.
Fix: detect the connected link, disconnect before setting the widget
value, queue the prompt, then restore the link after queue submission
completes. Defensively also update widget.inputEl, widget.element,
fire widget.callback, and setDirtyCanvas so all known widget-update
channels are hit.
Adds clear error messaging when no UltimateSamplerGrid node exists,
multi-node warning when there's more than one, and console logs at each
critical step for future debugging.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four bugs in 111e24d:
1. Dropdown positions were zoom-dependent / offset because JS overrode
the HTML's "position: absolute; top: 100%" with "position: fixed" +
getBoundingClientRect() coords. Inside the dashboard iframe (with
Comfy's transformed canvas as ancestor), position:fixed becomes
relative to that ancestor instead of viewport. Fix: wrap button +
menus in a position:relative anchor div (#quick-filter-anchor), drop
all JS coord setting, let CSS handle positioning.
2. Click-outside handler stayed registered after items used
e.stopPropagation, so reopening required toggling another UI element
first. Fix: named _qfMenusOutsideHandler with explicit
_qfAttachOutsideHandler/_qfDetachOutsideHandler pair; capture phase
(third arg true) bypasses stopPropagation; _closeQuickFilterMenus
now calls _qfDetachOutsideHandler; addQuickFilter calls
_closeQuickFilterMenus upfront.
3. Value menu was anchored to a hidden element (type menu hidden BEFORE
value menu opened), so getBoundingClientRect returned zeros and value
menu rendered at viewport (0, 6). Fixed by Bug 1 (CSS positioning);
anchorEl param dropped from _openQuickFilterValueMenu.
4. lora_strength wasn't in _qfAllTypes / _qfFacetableTypes /
_qfTypeLabels / _qfExtractItemValue / _quickFilterMatches. Now
selectable: extracts first lora's first strength from item.lora,
compares with 1e-6 float tolerance.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a third filter mode alongside main buttons / search / logic.
User clicks "+ Add Quick Filter" -> picks a type (LoRA/CFG/etc.) ->
picks a value -> adds a chip. Each chip can be expanded to show
"sub-filters": values of OTHER filter types that exist within the
subset matching this chip's primary. Click a sub-filter button to
add it as another chip (ANDed with existing).
Designed as faceted search: drill-down by selecting facets one at
a time. Each chip is independent and ANDed with the others (matching
the GitHub discussion's "logical && between boxes" semantic).
LoRA matching handles stacks ("name1 + name2"), so a "DMD2"
quickFilter matches items where DMD2 is one of multiple stacked
loras. Numeric types (cfg/steps/denoise/seed) use 1e-6 float
tolerance matching the Logic Filter convention.
Reset All Filters and the R key both clear quickFilters along with
the other filter mechanisms.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback: when 25/26 items already exist and only 1 new is being
generated, dashboard showed "job 14/26" or similar (skip path doesn't
update progress). Now displays effective progress: existing_count +
new_completed / total. So a session with 25 pre-existing shows 25/26
immediately, then 26/26 after the 1 new gen.
- Adds new_completed counter (separate from loop-index current_job)
- Replaces current_job with existing_count+new_completed in dashboard-
facing log/send_sync (lines 2107, 2114, 2123, 2133)
- Sends an initial progress update right after pre-flight so the user
sees the correct "already done" count before the first gen, clearing
any stale values from a prior run
- Other current_job references (LTX dispatch, image-gen log, start_at_job
gate, periodic save, final send, distributed mode) keep iteration-
index semantic — different contexts
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
User feedback: still triggers accidentally during panning. Reverts the
click-to-open feature added in 8790f97 (with timer-based fix in
4672116). REVISE button remains the only way to open the modal.
In-modal keyboard navigation (arrow keys, spacebar, 1-9 favorite, ESC)
added in 8790f97 stays intact — that part of the lightbox feature is
useful and uncontroversial.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Commit 490c519 added a webp/png/jpg dropdown but only flush_batch_with_vae
honored it. Three other save sites still hardcoded WEBP:
- flush_batch_with_remote_vae (image_generation.py)
- upscale_runner.py (3 sites in inline + deferred upscale paths)
- distribution_worker.py (PIL bytes shipped back to master)
Now all save sites read image_format from session_settings (orchestrator)
or the job spec (distributed) and match the format/extension.
Changes:
- remote_vae.py: RemoteVAEDecodeWorker accepts image_format kwarg; _worker
uses it to pick format/extension at save time (the real save site for the
remote VAE path — flush_batch_with_remote_vae only queues to the worker)
- generation_orchestrator.py: passes image_format to all RemoteVAEDecodeWorker
constructors (initialize_remote_vae + 3 per-config sites); adds image_format
to all 8 existing_data["meta"] writes so upscale_runner can read it from disk
- upscale_runner.py: reads image_format from manifest meta at thread start;
uses _fmt/_ext locals at all 3 save sites (seedvr2, latent-decode, PIL)
- distribution_manager.py: injects _session_image_format into worker job
config alongside existing _session_cooldown passthrough
- distribution_worker.py: reads _session_image_format from config; encodes
bytes in the chosen format; stamps _image_ext on meta so master uses it
- distribution_routes.py: pops _image_ext from meta to build filename with
correct extension before writing bytes to disk
Default behavior unchanged: all paths fall back to webp if image_format
is absent or unrecognised.
Code review found this in 490c519.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Code review found multiple inaccuracies:
- Instructed contributors to update a non-existent convertStateToConfigs()
JS function (was deliberately removed — single source of truth lives in
Python state_to_configs_json).
- Wrong line numbers for generate_config (~465 → ~837) and default state
(~80 → ~103); onConfigure migration reference replaced with the actual
async init block at ~484.
- Overstated safe_print module shadow scope (only 2 files, not all).
- Referenced missing LICENSE file (pyproject.toml declares it but the file
does not exist in the repo root).
Replaced with correct architectural guidance pointing to
state_to_configs_json, accurate line numbers, scoped safe_print claim,
and license info from pyproject.toml.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Card <img> previously had both onclick=openM and ondblclick=toggleFavorite.
Browsers fire click twice before dblclick, so double-clicking toggled
favorite AND opened the modal (twice). Mobile tap-to-scroll also
accidentally opened the lightbox.
Replace with a 250ms-delayed click handler (_cardImgClick) that
distinguishes single (open modal) from double (favorite). Remove
unused favIcon variable in the modal 1-9 keypress handler.
Code review found these in commit 8790f97 (lightbox feature).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
parseFloat-then-=== fails for common stored values (e.g. cfg=7.0
filter against item.cfg=7.0000000001 from float arithmetic). Use
1e-6 epsilon for both = and != operators.
Code review found this in commit bcadb7e (Logic Filter feature).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Clicking a card image now opens the Revise modal as a lightbox. Adds a
navigation bar with prev/next buttons and a position counter. Arrow keys
(← →), Space/Shift+Space navigate between filtered items; 1-9 favorites
the current image. Grid keyboard shortcuts are suppressed while the modal
is open to prevent conflicts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a Logic Filter panel in the filter modal with field/operator/value
dropdowns (supports lora_strength, cfg, steps, denoise, width, height,
seed). Multiple filters apply with AND logic. Chips display active filters
with one-click removal. Integrates into data pipeline via matchesLogicFilters()
helper. Reset All Filters also clears logic filters.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a 'Reset All' button to the filter modal header that re-selects all
filter buttons and clears search/logic filters. Updates the R key shortcut
to also reset the filter UI before reloading cards so visual state stays
consistent with the data.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a 'Delete All Rejected Items' button in the cog menu next to the
existing 'Delete All Non Favorited Items' button. Adds the matching
/config_tester/delete_rejected backend endpoint that removes rejected
item files and updates the manifest.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a dropdown to the Builder UI's Run Settings section letting users
pick the saved image file format. Default remains webp (current
behavior). State plumbed through state_to_configs_json ->
_session_settings -> orchestrator -> save_image_to_disk.
PNG users will see lossless saves; JPG users smaller files at quality 95.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Welcomes PRs, documents dev setup, test commands, key architecture
notes (single-source-of-truth transformer, manifest field names,
V1/V3 node dispatch), and a PR checklist.
Closes user request from GitHub feedback thread.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Scans the planned configs list once before the generation loop and
counts how many already have output files on disk (the same skip path
used by check_if_job_completed). Displays the count in the running
ETA line: "job X/Y (P%) | ETA: ... | N existing".
Helps users gauge how much actual work remains when re-running a
session that's been partially completed in a prior run.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CSS rules and variables (--accent-steps/cfg/upscale/media) were already
added in 2c3851e; user reporting the bug was viewing a stale inlined dashboard
HTML generated before that commit. Since html_generator.py inlines report.css
at generation time, regeneration is required for CSS edits to take effect.
Added belt-and-suspenders box-shadow: inset to the four filter types so that
even if a theme override or cached page interferes with background, the inset
ring remains visible. Added a code comment documenting the regeneration
requirement.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the user types '0.5, 1.0' they want 2 outputs (lora at 0.5, lora
at 1.0 — both model and clip mirroring), not 4 outputs (Cartesian
m×c). The bug was that we wrote both _model AND _clip side-channel
arrays, so Python emitted 'name:[m]:[c]' which the orchestrator
correctly interpreted as Cartesian.
JS: collapse the two-input compare section to a single 'Strengths:'
input. Never write _clip to lora_weight_arrays.
Python: process_lora_array now emits 'name:[m_arr]' (no clip part)
when only _model array is present. The generator's existing
parse_lora_definition defaults clip=model when the third segment
is missing, giving N configs with locked semantics.
Drops the Cartesian path entirely. Backward compat retained: if a
saved workflow has both arrays (legacy), the dual-bracket form is
still emitted so the orchestrator can Cartesian-expand as before.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces the two separate '+ Compare Strengths' / '+ Compare CLIP'
buttons with a single button that toggles a section showing two
labeled inputs ('Model Strengths:' and 'Clip Strengths:'). When
Strength Lock is ON, only the Model input shows (CLIP mirrors
automatically — same as today).
Also fixes button-text-not-updating bug: previously the button
required a full re-render to swap between '+ Compare Strengths' and
'✕ Array' text, so toggling the input visible left the button stuck
at '+ Compare Strengths' until page reload. Activation now persists
arrays as [1] (Python passes through unchanged) and uses array
existence rather than length>1 as the active indicator, so reloads
correctly restore the active state.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
convertStateToConfigs() was deleted in the previous commit; the JS
preview now POSTs to the new endpoint and Python is the sole transformer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces synchronous JS convertStateToConfigs (~280 lines) with a
debounced (800ms) fetch to the new endpoint. Preview now renders
literally what generate_config produces — drift impossible by
construction.
Stale-state badge surfaces network failures without blocking editing.
Loading placeholder appears during the first-paint debounce window.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both runtime (generate_config) and preview now invoke
state_to_configs_json — single source of truth, drift-proof.
The Builder UI will fetch this endpoint instead of running a
parallel JS transformer (next commit removes that JS code).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Covers single-lora pass-through, multi-lora combine with ' + ',
folder ref un-expansion, folder+brackets excluded from combine,
bypassed lora exclusion, and ltx_video model_type propagation.
Also tightens the existing bracket-array test from substring match
to exact string equality and removes the unused pytest import.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eliminates the JS<->Python drift bug class by making one Python function
the source of truth for state -> configs_json. Both the runtime
(generate_config) and the new preview endpoint (next commit) call it.
Adds golden test for the lora-strength-arrays bracket case that just
broke (regression guard).
Also loads the real config_builder_node module in conftest.py (instead
of a bare stub) so that tests against UltimateConfigBuilder methods work
correctly. Stubs network_utils before loading to satisfy the relative
import.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>