Loading a workflow does not re-fetch extension JS, and aiohttp serves
web/*.js with only Last-Modified (no no-store), so an open tab can keep
running a cached old text_gate.js. Log a build tag on setup so we can
tell from the devtools console whether the persistence/weighting build
is actually loaded.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ComfyUI's "edit attention" (wrap selection in (token:weight)) is a global
window keydown listener that acts when a <textarea> is focused. The text
gate editor is a textarea, but its keydown handler called stopPropagation
on EVERY key, so the event never bubbled to window and weighting never
fired — notably when using the node as a prompt text node in protected mode.
Now stopPropagation is skipped for the weighting shortcut (Ctrl/Cmd + ↑/↓)
so it reaches the global handler; all other keys are still stopped so
typing/space can't trigger litegraph canvas shortcuts. The weighting edit
goes through execCommand, which fires our oninput -> stored_text stays synced.
Verified against the verbatim editAttention from the shipped frontend:
whole-word weighting, existing-weight decrement, and no-selection word
expansion all round-trip; plain keys stay stopped.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The editor content was only restored on reload in protected mode, and
stored_text was only synced on keystroke (oninput). So in the default
pause mode edited text came back empty after refresh/reload-workflow,
and upstream text passed without a keystroke was never captured.
Now applyPersistedMode restores the editor from stored_text in BOTH
modes, and syncStored also fires when upstream text arrives (socket)
and on Pass — so whatever text is shown/edited survives a reload.
Verified against the shipped litegraph serialize()/configure() widget
semantics: default-mode + pass-without-typing round-trips now restore.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
GateBus.wait() only checked the gate's own Stop flag, so pressing ComfyUI's
Interrupt left the image gate blocked. Add should_cancel to wait() (mirroring
wait_payload) and pass mm.processing_interrupted from gate.py, matching the
text gate.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
computeSize alone left the collapsed pill visible in the 1.47 frontend.
Set widget.hidden=true (what getVisibleWidgets filters on), matching the
pool node's hideWidget. Value still serializes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Menu category on all nodes now reads 'Dataset Gates', matching the repo name.
Internal identifiers (routes, socket events, extension ids) left unchanged.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sidecar chains {content,name,ext} specs over a SIDECAR-typed link; the save node
mirrors SaveImageKJ (folder_paths.get_save_image_path) and writes each sidecar as
base+name+ext next to the image. build_plan validates the whole chain before any
I/O so duplicates/bad extensions write nothing.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a '🔒 Protected (text node)' toggle. When on, the DOM editor is a free text
box whose value mirrors into the hidden stored_text widget; the node outputs that
text and ignores upstream (no pause, socket events ignored). Persists via the
protected + stored_text widgets; restored on configure. stored_text is single-line
so it hides cleanly (pool_id trick).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
protected=True makes run() emit stored_text and ignore upstream with no pause;
IS_CHANGED caches on stored_text when protected (NaN otherwise). text input is
now optional so the node can run standalone.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Same latent bug as the text gate: a bare app.queuePrompt(0,1) enqueues but
doesn't kick off execution in the 1.47 frontend. Execute the Comfy.QueuePrompt
command (the Run button's path), with app.queuePrompt as a legacy fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A bare app.queuePrompt(0,1) enqueues but skips the command-level run setup in
the 1.47 frontend, so the prompt never kicked off — you had to press Run
manually. Execute the Comfy.QueuePrompt command (same path as the Run button /
Ctrl+Enter) instead, with app.queuePrompt as a legacy fallback.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Run-from-here now preserves the edited text via an explicit _tgKeepEdit flag
set when the button is pressed, instead of comparing incoming vs last text.
A non-deterministic upstream (random/seeded prompt) regenerates text on every
re-queue, which made the old comparison clobber the edit. Normal toolbar Queue
still shows fresh upstream text.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Multi-lane any-type pass-through node ("rail"): in_i -> out_i, empty lane
-> ExecutionBlocker; +/- to add/remove lanes (bottom in P1, top with
wiring-preserving remap in P2). Pure build_outputs + shared AnyType; lazy
comfy import keeps it unit-testable. No IS_CHANGED (transparent passthrough).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Auto-snaps images onto ÷64 ≤1.64MP training buckets (cover + center-crop,
Lanczos), transforms an optional mask identically, outputs width/height/label.
Pure bucket math tested against KLEIN_BUCKET_SIZES.md. 99 tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Auto-snap images onto ai-toolkit training buckets (W×H ÷64, ≤1.64MP) via
cover-scale + center-crop (Lanczos), per KLEIN_BUCKET_SIZES.md. Pure stdlib
bucket math (reproduces the spec table) + a torch node that also transforms
an optional mask identically and outputs width/height/label. No frontend.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Disk-backed image bus (input/gate_bus/<id>/): gates auto-publish image+mask
to a named send_id on pass; when image input is empty they load from get_id
(dropdown) — wireless, cycle-free "restart from the gate point" across runs.
Making image optional implements ignore-on-normal-path. TDD plan with a pure
stdlib imagebus + tensor savers; comfy imports stay lazy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Document all five nodes (Image Pool, Pool Profile, Folder Image Loader,
Image Gate, Text Gate) with IO tables and behavior, plus shared concepts
(blocking gates, mask polarity, storage/profiles layout) and dev layout.
Refresh the stale pyproject description.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Connecting a Pool Profile no longer overwrites the pool's pool_id. The pool is
switched only when the user actively selects a profile in the dropdown; picking
an empty profile while a pool with images is connected offers to copy those
images into it (new seed_profile op + /grid_pool/profiles/seed route), so the
current pool is never silently lost.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>