Manager installs the pack in custom_nodes/ausboss-nodes and a git clone
lands in custom_nodes/ComfyUI-AusBoss. With both folders present, both
copies registered GET /ausboss/pack_modules and ComfyUI stopped at
startup: "Added route will never be executed, method HEAD is already
registered".
The first copy to load claims a marker in sys.modules. A later copy
loads no node modules, registers no routes, serves no js folder, and
prints which folder it skipped.
The job carried the 0.1.0 note as a fixed string, so every later
version would have shown "Initial release" on the Registry. The note is
now a required input of the manual run, and the publish step refuses a
blank one.
Mask by Touch keeping picks on a new picture is a new feature, so this
release is 2.23.0 rather than 2.22.1. It also brings the stopped-run
message fix and the corrected example notes.
A run that stopped at the node left its message on the face until
something else was said there. The face's own runs cleared it, but a
workflow run's result did not, so the message stayed under a good result.
The stop now marks its message, and the next result that comes in
clears it.
A different picture coming in no longer stops the run with every pick
dropped. The node moves the clicks and paint onto it, in the same places,
scaled from the size in their key to the new picture's size, and sends the
face the moved touches. The face takes them while it still holds the picks
they were moved from. New picture, under More, set to start over keeps the
old behaviour.
- Krea 2 Rotate + Outpaint: the note describes the tone match the
graph runs.
- Qwen Image 2.1 Inpaint: the note says the seed is fixed.
- Qwen Image 2.1 Upscale + Restore: the Megapixels note mentions the
roughly 2 MP minimum redraw.
Newer ComfyUI stamps a widget's input with its row position (slot.pos) and keeps using it after the widget is gone, so fixed_frames on Video Crop + Rotate + Pad -> Clip landed on force_rate. liftSocket now clears it; Resolution uses the same helper.
ComfyUI reports newer dropdowns as "COMBO" instead of a list of choices,
so saved values for them were ignored and four examples were flagged as
missing required inputs they do have.
Review fixes for Workflow Switches.
- A switch that changes a setting on a node no longer turns that node
off.
- LoRA choices keep every differing field and show row and group locks.
- Odd ids or titles in a saved workflow no longer break loading, and
unsafe group colours are handled.
- The add-choice button appears as soon as a node is selected.
- Copy clones no longer leak redraw timers.
Tests: tests/workflow_switches_frontend.test.mjs and
tests/workflow_switches_regressions.test.mjs, with additions to
tests/workflow_switches.test.mjs.
Not covered: repointing switches inside copied subgraphs. The review
wrote a test for it, but nothing delivers per-subgraph id maps yet, so
that test is left out.
No inputs, outputs or mapping keys change.
Review fixes for the pack's offline checks.
- The startup version reader and the checks read pyproject.toml as TOML
instead of a regex.
- validate_nodes and registry_contract catch more ways a node mapping
or the module list could be hidden from registry scanners.
- release_preflight flags network-client imports and spaced or
qualified fetch calls in shipped code.
- workflow_contract verifies saved widget values against the real
widget order, covers subgraphs and validates model download info.
- Krea 2 Inpaint Masked example: two Note nodes' named widget values
now match their positional values, which the new check requires, and
the file ends with a newline. The graph itself is unchanged.
Tests: tests/test_check_regressions.py, with one adjusted case in
tests/test_workflow_contract.py.
No inputs, outputs or mapping keys change. No version change.
Review fixes for the colour helpers and the Krea 2 core.
- Color Match (MKL and histogram) returns the image unchanged when the
reference is the image itself or a same-colour reference of another
size.
- Krea 2 outpaint stops reusing a stale reference cache: the cache is
keyed by reference and canvas and cleared on in-place edits and at
sampler start and cleanup.
- Other model patches (attention hooks) now apply inside the Krea 2
outpaint path, in order, with their metadata.
Tests: tests/test_color_match.py gains the identity cases;
tests/test_krea2_core.py covers the cache and the patch order on the
CPU.
Color Match numbers change slightly for the same inputs. No inputs,
outputs or mapping keys change.
Review fixes for the preview-carrying nodes.
- Compare and the input preview bar no longer rebuild their panel when
a workflow loads, and a late Align or Save reply is ignored after the
panel is removed or the picture changes.
- The preview bar clears its picture when the preview switch is off
and works when two nodes watch the same source.
- Save Image forgets the old saved path when the folder changes or a
run saves nothing, and can no longer pick the same file number as
Compare at the same time.
- Save Image accepts bfloat16 and single-channel images.
- A blank linked caption writes no caption file, and a caption file
that already exists follows the "on existing" setting.
Tests: tests/preview_lifecycle.test.mjs covers the panel and reply
cases; tests/test_image_save_helpers.py covers the image types, the
numbering and the caption collisions; two cache tests now call the
middleware directly instead of starting a test server.
Caption behaviour changes as described above. No inputs, outputs or
mapping keys change.
Review fixes for the inpaint crop and stitch helpers.
- The crop window now contains the full feathered edge, so the blend at
the seam is complete.
- Outpaint extension bands stay fully paintable after the mask is
shrunk or softened.
- The reported blend mask no longer extends past the area actually
pasted.
- The Crop for Inpaint, Stitch Inpaint and Realign to Source help pages
are corrected (Multiple default, Max zoom wording, resizing caveat).
Tests: tests/test_inpaint_crop_helpers.py covers the feather, the
bands and the partial paste window; tests/inpaint_help.test.mjs checks
the help pages against the code; the classic pin test keeps comparing
against the released output.
Crops and blend masks differ slightly from earlier releases for the
same inputs. No inputs, outputs or mapping keys change.
Review fixes for the Image Folder node and its routes.
- Switching folders or reloading a workflow no longer shows stale or
mixed-up results: late replies are ignored, copy and upload locks
hold, and removing a node closes only its own popups.
- A very large folder keeps its newest files instead of an arbitrary
slice.
- Thumbnails refresh after a picture is rewritten in place.
- A run over more than 64 pictures marks every picture as done.
- Route errors no longer reveal server paths.
Tests: tests/image_folder_lifecycle.test.mjs and additions to
tests/test_image_folder.py cover each case; the frontend cache and
media upload tests now call the handlers directly instead of starting a
local server.
A run's completion note carries an extra context field and lists every
loaded picture. No inputs, outputs or mapping keys change.
Review fixes for the canvas extensions.
- A node colour picked from the right-click menu, Theme default
included, now stays after save, reload and paste. An invalid Custom
colour no longer overwrites the last good one.
- About this node appears in the right-click menu in Nodes 2.0, where
the ? badge does not work.
- Resizing a node by its corner in Nodes 2.0 snaps to nearby lines;
Shift bypasses the snap.
- A plain click on a resize handle no longer resizes the other selected
nodes in Resize Together.
- Sideways mouse-wheel scrolling stays inside a code block that can
scroll sideways.
Tests: tests/canvas_extensions.test.mjs runs the real extension code
against a fake page for each case above; tests/canvas_passthrough.test.mjs
gains the horizontal-wheel case.
Appearance now saves the chosen colour as a node property (ausbossColor).
No inputs, outputs or mapping keys change.
* Restyle widget cards in solid gray on one shared palette
Cards, number boxes and off|on switches take their colors, font and radii
from js/shared/theme.mjs. Controls are solid gray with no outlines on a
near-black card, grays are neutral, faint text holds 4.5:1, and number boxes
in a card are as tall as the row. A test keeps colour literals out of the
three modules and checks text contrast.
* Lift the card toward the node colour and brighten the off mark
* Restyle the picture bar, its stepper and switch on the shared palette
* Darken the card a step
* Picture frame back to the lighter well; changelog covers the picture bar
It quietly filled holes I wanted to keep, and it's folded under More so
you don't see it's on. New nodes now start with it off. Saved workflows
keep whatever they had. The Mask by Touch example is saved with it off.
A sentence like "Replace the man in the red shirt with @1" now names only the person who goes. Every swap with a picture asks for the replacement from the first frame. A text model on clip writes the @1 looks like line for hair and face when none is typed.
Publish matching pack and frontend versions, release notes, and automatic GitHub Releases after Registry uploads. Point the README badge at the latest release.
* Group crop controls and put video scrubbing below the preview
* Stop IN where the connected clip length still fits
* Add play and pause beside the crop scrubber and clarify resize padding
* Preview the current brush beside Size in Image Compare
* Center the crop and its padding together on each axis
* Polish crop controls and load images from Outputs
* Add crop controls preview
* Add Load Audio node: wave, trim, preview and max length
Load a sound file and keep only the part you want. The node shows the
whole file as a wave. Drag the IN and OUT handles (or type the times),
press play to hear just that part, and set Max length to the longest
sound the next step takes: anything past it turns red and is cut off,
so you see what comes out before you run. Max length can be wired.
Only the picked part is decoded. Outputs the audio and its seconds.
Help page, README, changelog, Chinese names, tests and the API snapshot
are included.
* Load Audio: open smaller and go down to 280 wide
* Load Audio: shorter wave, length moves off the wave next to OUT
* Add the Pika H3 Flex example workflow
* H3 Prompt Writer: a swap puts each person picture in one person's place
* H3 Prompt Writer: keep quoted words in a swap and read who goes out of a longer line
A swap dropped every typed line once it had read who goes, so words in
quotes never reached H3 and the new person's lips were guessed. Those
lines now stay as the new person's words, landing on the lip movement of
the video, and a voice with a swap no longer says nobody speaks.
"Elon is @1, he replaces the person at the counter" went whole into the
description of who goes. The words after replaces (or takes the place of,
instead of, ...) are now who goes, and a name given to @1 is left out.
* H3 Prompt Writer: swap lines go to the right speaker
Names given with a comma ("Altman is @1, the man on the left. Dario is
@2, ...") no longer run into each other, and a later "Dario says" or
"He says" goes to that person. A thing that speaks, like the card
reader, says its words from off screen so nobody's lips move. "raps"
and a few other ways of speaking count as speech, so "and raps" stays
out of who goes.
* H3 Prompt Writer: keep how a swap line is said, and its exact time
"The card reader says in a flat robot voice" now names the card reader
and keeps "in a flat robot voice" as how it says the words. A time like
3.2s at the start of a line was read as a list number and lost its 3; it
now stays 3.2, a time typed inside a swap line counts too, and the first
words of a swap say when they start.
* H3 Prompt Writer: a person off screen gets their own voice
"A woman off screen says" went to the person on screen, as their voiceover,
and in a swap it landed on the swapped-in person. Now a person or a name
the line puts off screen (off screen, off camera, behind the camera, O.S.)
speaks from off screen as their own speaker, in a swap and outside one.
An unnamed voiceover, an @ or a he or she still stays the main person's.
Off-screen voices no longer get a 'stays silent with the lips closed' line.
* H3 Prompt Writer: a swap can keep the clip's clothes
A swap always dressed the new person in the picture's clothes. Typing
"keeps her outfit", "in the clip's clothes" or "keep the clothes from the
video" now takes only the face and hair from the picture and keeps the
clothes of whoever goes. "keeps her own clothes" still means the picture's.
* H3 Prompt Writer: a thing said to be off screen speaks for itself in every mode
"A card reader off screen says" got its own voice only in a swap. In copy
its moves and the other modes it became the person on screen's voiceover.
Anything a line puts off screen now speaks for itself; "a voice" and
"narrator" stay the main person's voiceover.
* H3 Prompt Writer: a swap that keeps the clip's sound gives the new person only their own lines, timed from that sound
With the clip's own sound under a swap, H3 heard every voice and moved the
new person's mouth to lines that weren't theirs. The new Clip's own sound
switch leaves out anyone else's words, says the new person keeps the lips
closed between their lines, and puts each untimed line where speech starts
in the video's sound. A typed time still wins.
* H3 Prompt Writer: tell voices apart by pitch when timing lines from the clip's sound
Loudness gaps alone put the new person's lines on the other speaker's words
when two people trade lines quickly. Each line now takes a run of speech
that fits its syllables, starts after a quiet, and keeps one voice at one
pitch. On the lip sync clips this lands every line within 0.15 s of
Whisper's times, except one soft word under a crowd.
* H3 Prompt Writer: a short name for who goes in a swap keeps their line
"The man in the car" for "the man in the back of the police car" was read
as someone else, so with the clip's sound on his line was left out. A
speaker is who goes when every word naming them appears in who goes.
* H3 Prompt Writer: a swap with the clip's sound and no words typed speaks as the video does
With nothing in quotes, it said the new person keeps the lips closed and
says nothing, even when the clip is full of speech, and the soundscape
was the place's steady sound. Now speech in the clip means the new person
speaks as in the video, and the soundscape is the clip's own sound. With no
speech heard and no words typed, it says nothing about the lips.
* Document two-person swaps and off-screen dialogue
* Add Load Audio node: wave, trim, preview and max length
Load a sound file and keep only the part you want. The node shows the
whole file as a wave. Drag the IN and OUT handles (or type the times),
press play to hear just that part, and set Max length to the longest
sound the next step takes: anything past it turns red and is cut off,
so you see what comes out before you run. Max length can be wired.
Only the picked part is decoded. Outputs the audio and its seconds.
Help page, README, changelog, Chinese names, tests and the API snapshot
are included.
* Load Audio: open smaller and go down to 280 wide
* Load Audio: shorter wave, length moves off the wave next to OUT
* Add the Pika H3 Flex example workflow
* H3 Prompt Writer: a swap puts each person picture in one person's place
* H3 Prompt Writer: keep quoted words in a swap and read who goes out of a longer line
A swap dropped every typed line once it had read who goes, so words in
quotes never reached H3 and the new person's lips were guessed. Those
lines now stay as the new person's words, landing on the lip movement of
the video, and a voice with a swap no longer says nobody speaks.
"Elon is @1, he replaces the person at the counter" went whole into the
description of who goes. The words after replaces (or takes the place of,
instead of, ...) are now who goes, and a name given to @1 is left out.
* H3 Prompt Writer: swap lines go to the right speaker
Names given with a comma ("Altman is @1, the man on the left. Dario is
@2, ...") no longer run into each other, and a later "Dario says" or
"He says" goes to that person. A thing that speaks, like the card
reader, says its words from off screen so nobody's lips move. "raps"
and a few other ways of speaking count as speech, so "and raps" stays
out of who goes.
* H3 Prompt Writer: keep how a swap line is said, and its exact time
"The card reader says in a flat robot voice" now names the card reader
and keeps "in a flat robot voice" as how it says the words. A time like
3.2s at the start of a line was read as a list number and lost its 3; it
now stays 3.2, a time typed inside a swap line counts too, and the first
words of a swap say when they start.
* H3 Prompt Writer: a person off screen gets their own voice
"A woman off screen says" went to the person on screen, as their voiceover,
and in a swap it landed on the swapped-in person. Now a person or a name
the line puts off screen (off screen, off camera, behind the camera, O.S.)
speaks from off screen as their own speaker, in a swap and outside one.
An unnamed voiceover, an @ or a he or she still stays the main person's.
Off-screen voices no longer get a 'stays silent with the lips closed' line.
* H3 Prompt Writer: a swap can keep the clip's clothes
A swap always dressed the new person in the picture's clothes. Typing
"keeps her outfit", "in the clip's clothes" or "keep the clothes from the
video" now takes only the face and hair from the picture and keeps the
clothes of whoever goes. "keeps her own clothes" still means the picture's.
* H3 Prompt Writer: a thing said to be off screen speaks for itself in every mode
"A card reader off screen says" got its own voice only in a swap. In copy
its moves and the other modes it became the person on screen's voiceover.
Anything a line puts off screen now speaks for itself; "a voice" and
"narrator" stay the main person's voiceover.
* Document two-person swaps and off-screen dialogue
How the selection is laid out decides what it shares: nodes stacked in a
column get the grabbed node's width, nodes side by side in a row get its
height, and a grid gets both. A pull on the top edge keeps each bottom
edge in place.
Integer and Float get the same gear as Text, with a big look: one large centred number with its arrows, in a taller row. The Pika H3 Flex workflow uses it for Seconds.
The look is kept in the node's properties (ausboss_text_look: "big"), so saved values and what the node sends don't change. A row with its own height in a look now takes that height when the look changes. Docs and changelog updated.
With two or more nodes selected, pulling the edge or corner of one now
gives every selected node its width, in the classic look and Nodes 2.0.
A pull on the left edge keeps each right edge in place. Heights are left
alone, one Ctrl+Z undoes it, and Settings > AusBoss > Canvas > Resize
together turns it off.
- Words light up as you type (@1, times, cuts, quotes, labels), with @
autocomplete and a ? cheat sheet
- Timeline bar shows every picture a line names; drag a block to reorder,
drag its edge to set its time, click an edge for a cut, click a block to
jump to its line
- Sound and Music boxes (new optional inputs, appended) fill H3's
overall_soundscape and non_diegetic_music
- Pictures and video as chips with their jobs; Length, Camera and Look stay
plain rows so they can still be wired
- Help page and changelog updated; tests for the colors, autocomplete,
timeline edits and the new inputs
locales/zh/nodeDefs.json gives every public node a Chinese name,
description and input and output names, which ComfyUI shows when its
language is set to Chinese. Cards and help pages stay English.
tests/test_locales.py keeps the entries matched to the node API, and the
preflight lets locales/ ship.
Image Crop + Rotate + Pad's stitcher now remembers the picture and the
crop's position. With Paste onto on original, Stitch Inpaint lays the
result on the whole picture, straight, resized or turned. The default
stays crop, so saved workflows run as before.
Two named settings used to take about 20 clicks through a menu and
Settings. + Choice saves how a node is set under a name, and the second
one turns the card into a pill. What a choice changes is what it holds
differently from the other choices, so it needs no off side. A choice can
also hold the nodes only it needs, so a missing model in the branch nobody
picked no longer stops the run.
js/docs/agents/workflow_switches.md tells an AI agent how to write a card
into a workflow file. It sits under js/docs because that ships with the
pack. A test holds its examples to what the card really stores.
A LaMa model file can run code when it opens, so LaMa Inpaint now checks
the file first and stops on anything it doesn't know. Frame Interpolate
errors no longer show server folders, Image Folder's "start over" keeps
going past the last picture, and One at a time in Workflow Switches holds
for new switches and for the setting being turned on.
A new switch read on beside the rows already on, and turning the setting
on left every row on. A new switch now gives way to the rows that were
there, and turning the setting on keeps one row on.
Fixes#256
A LaMa model file can run code when it opens, so the node checks the file's
SHA-256 first and opens only the two common builds of big-lama.pt. Frame
Interpolate's errors now name the file, and the folder goes to the console.
With "Run" -> "one per run", "After a run" -> "next" and "At the end" -> "start over", Picture
stopped at count + 1 after the last picture. The backend maps that to
picture 1, but nextPosition() capped it at count + 1 again, so every
following run loaded picture 1.
nextPosition() now takes at_the_end and correctly wraps the number around after
the last picture.
The paste fix in 2.20.3 works when the paste happens, so a workflow pasted
and saved on an older version keeps its old numbers and still reads
"empty" after an update. The word's hover text now says what happened and
what to do: open the original file again, or add nodes from the switch's
menu. The help page says the same.
Copy and paste give every pasted node and group a new id, and so do
duplicate, Alt + drag and dragging a saved workflow onto the canvas. A
Workflow Switches card remembers its parts by id, so a whole workflow
pasted into a new tab read "empty" on every row and nothing could be
switched on; pasted into another workflow it could switch unrelated nodes
that had the old numbers. A Callout's links to a node or group broke the
same way.
The frontend's paste (LGraphCanvas._deserializeItems) returns which new
node each old id became. js/shared/paste_ids.mjs hands that on after every
paste, and a pasted card or note points at the copies of what it names.
An id the paste did not copy is kept, so a card pasted on its own still
switches the original parts.
While the models load, an empty picture area says the run is going instead
of asking for a run. With steps on, the last step stays up until the saved
picture has loaded, so it no longer blinks out. Between a tiled upscale's
split and stitch (and for Ultimate SD Upscale) the step pictures are tiles:
the whole picture stays and the chip says "upscaling in tiles".
Stitch Inpaint takes an RGB patch over an RGBA picture and keeps the
picture's own alpha, instead of stopping on a channel mismatch. The halo
fix spreads colours only.
Image Compare keeps alpha in a preview that has see-through parts and
flags it, so the panel shows them over a checkerboard (Slide, A/B and
Erase). The Erase mix lays the top picture over the other, the saved PNG
is RGBA, and the PSD carries each layer's transparency plus a merged
alpha, its flat colours stored over white as Photoshop does. A see-through
B is resized premultiplied. Align measures over gray and warps all four
channels. Solid pictures give byte-identical files.
Refs #232
When the picture coming into the node changed after picking, the run
stopped with "nothing is picked yet" while the picks still showed on the
node. It now says the picks were made on another picture. If nothing set
to empty mask still lets the run go on.
Watch the picture form one sampling step at a time and stop a run that is
going wrong. Step Preview needs no wires and shows whichever sampler is
working, with Stop and Stop all. Save Image and Save Video get a steps
switch, off by default, that shows the steps where the saved picture goes,
with a Stop button.
A queued workflow that shows its steps asks ComfyUI for the auto preview
method for that run, only when no method was chosen in the frontend or on
the command line.
The button still reads Civitai lookup failed, and a message now gives the
reason. A browser that cannot reach civitai.com at all names the usual
causes: a firewall, a VPN, or ComfyUI started with --disable-api-nodes.
The browser downloads a small copy of the LoRA's first picture from
Civitai's picture server (PG-13 or milder unless the gear menu's Civitai
picture option says any), and the server saves it beside the LoRA as
<model>.preview.jpg, never replacing a picture. The LoRA Previews add-on
is no longer needed, so its hooks and install hint are gone.
After Fetch Civitai info saves its file, LoRA Loader sends a browser
event. The separate LoRA Previews add-on answers it by saving the LoRA's
preview picture, and the card and hover show it without a reload. The
info card links to the add-on when a LoRA has Civitai info but no picture.
Turning a group off stops its LoRAs and keeps each row's own on/off for when
the group comes back on. A switch saved with a group on and off now moves only
the group, shows one lock on the group's header, and leaves the rows inside to
the person. Renaming the group keeps the switch; clicking the lock takes the
group back.
Shift-click a tick box or name ticks every file from the last one ticked.
Ctrl+Enter ticks the highlighted file and Shift+Enter ticks the run up to it.
- The picker's arrow keys no longer stick when the list opens under a
pointer that has not moved: only a real pointer move takes the
highlight.
- A group's only LoRA can be dragged out, and a group's first row
dropped above its own header lands there instead of at the bottom.
Just below a group's last row is now always outside the group.
- Right-click on the space around the rows opens a stack menu (add,
group the selection, fold or open all groups, delete all) instead of
the browser's Copy / Select all; the group menu also ends with
Delete all.
The + LoRA list gets a tick box beside each file. Ticked files (also by
Ctrl/Shift-click or Ctrl/Shift+Enter) are added together from a bar at
the bottom; ticks survive a search, and files already in the stack read
"in stack".
Rows can be grouped: Ctrl/Shift-click to select, right-click to group,
move to a group or take out. A group header has a switch for all its
rows, a fold arrow that shortens the node, a double-click rename, and a
grip to drag the whole group. Rows drag in and out by their grip. The
group name is stored on each row, so the stack still runs top to bottom
and groups travel with templates and undo; folded groups are a node
property.
A pack that wraps node validators in a catch-all takes **kwargs, so
ComfyUI hands it every input and it passes them all on. Load Image + Pad
then stopped the run with "VALIDATE_INPUTS() got an unexpected keyword
argument 'pad_left'" (#229), and every other validator in the pack would
fail the same way.
ignore_extra_inputs drops the inputs a validator does not name and shows
ComfyUI the original signature, so on a plain ComfyUI nothing changes:
only the named inputs are passed, ComfyUI still runs its own range and
list checks on the rest, and a failed check is still reported once.
Every validator in the pack wears it, and a test holds new ones to it.
In the classic renderer a picture's card dragged from ComfyUI's Assets
panel loaded into Image Crop + Rotate + Pad only when dropped on the
node's title bar. Dropped on the picture or the controls, the drag never
reached the canvas, which is where ComfyUI finds the node under a drop,
so nothing happened.
The pack already hands a file dropped on one of its panels to the node
underneath. An Assets card carries no file, only
application/x-comfy-asset-info, so that drag now takes the same path.
Nodes 2.0 already handed it to the node.
A text link held over Mask by Touch snapped to its hidden `touches`
input, drawn at the top edge of its settings card, and letting go wired
the text in place of the picks. Image Folder's hidden `pictures` did the
same once Folder was wired. The frontend gives every widget an input
socket unless it is marked socketless, and a link dropped on a node
lands on its first free input of the link's type, hidden ones included.
The card's `socketless` option, which only made a combo, now also makes
a one-line text widget for a STRING (markSocketless moves to
widget_card_math.mjs so it can be tested). Mask by Touch's card lists
touches and ask, Image Folder's lists pictures. The backend inputs do
not change: saved values still load by position and the API prompt
still carries them. An older workflow brings the empty slots back on
load and the card drops them again, as it already did for Seam.
Image Crop + Rotate + Pad, Load Image + Pad and Load Video refused a
picture named the way ComfyUI names a generated file
("photo.png [output]", "[temp]") with "The selected source is outside
ComfyUI's input folder." Core Load Image takes those names.
resolve_input_path now reads the name the same way the video nodes'
Outputs list already does (resolve_server_file): the tag picks the
input, output or temp folder, and the rest is a name below it. A whole
path, "~", "." and "..", and a network path are refused from the text
alone; a link that leads out of the folder is refused once resolved.
Both functions share that code. A whole path inside the input folder,
accepted before, is now refused too, as the security rule asks.
Load Video's player and the shared upstream preview stripped the tag
and asked /view for the input folder; they now keep the folder.
Tests cover input, [input], [output] and [temp] names through the
resolver and through ComfyUI's own validation next to core Load Image,
and the refused escapes. Help pages, SECURITY.md and CHANGELOG say so.
The node gets a second output, image_b: the B batch, untouched, the
way image_a already passes through. image_a stays the first output, so
saved workflows keep their wires. The slider, its resting side, Erase
and Save are unchanged.
Help page, tooltips, README line and CHANGELOG say so; the node API
snapshot gains the one new output.
A round knob above the chosen box turns it (Shift: 5 degree steps); a Turn
field under the canvas types the angle and a double-click levels it. The
mask follows the turned box exactly, bboxes and elements keep the straight
box around it, and a turned text box adds how it is tilted to its
description. A box that is not turned saves and outputs exactly as before.
The arrow keys move a box one pixel (Shift: ten), and the grid starts off
when a picture is wired in. Handles, tags and outlines keep one size on the
screen at any zoom, and over a picture the boxes are see-through. Each box's
tag says whether it is text or an object, a chip per box picks the box to
edit, and the type buttons read as that box's own setting. A gear sets the
grid size. With a picture shown, the canvas keeps the picture's own shape
even when width and height are wired.
The mask is white inside every box and black elsewhere, at the size of the
picture on background (or the canvas without one). Draw a box over the words
on a sign, wire the mask into an inpaint chain, and only the words change.
It comes after the other three outputs, so saved workflows keep their wires.
Draw boxes on a canvas and say what goes in each one: a thing, or words to
write in the picture. Wire elements to MOSS-Image Layout Prompt and the
picture puts each thing where its box is.
It gives what ComfyUI's own Create Bounding Boxes gives (preview, bboxes,
elements; same types, same order, same values from the same boxes), so it
can take that node's place, and its canvas also draws in the classic node
look. The boxes are kept as parts of the canvas in a hidden socketless
string, so a wired size never moves them.
A switch that turns LoRA Loader rows on and off compared and wrote each
row's strength as well as whether it is on. A row turned down to 0.6 by
hand read "changed" (a pill went amber, "only partly on"), and the next
click set its model strength back to the saved one and left its CLIP
strength behind.
A LoRA row pair now carries whether the row is on, and a strength (model
or CLIP) only where the switch's saved on and off differ. pairNow compares
only what the pair carries and pairValue writes only that, so the strength
and triggers someone set stay, and the card reads on or off. A switch
saved with one strength for on and another for off still sets it, now
with the CLIP strength too. How switches are saved does not change.
Edit models often draw the picture a few pixels to the side or a little
zoomed in, so the edges of the paint in Erase show a step. A new ALIGN
button in Erase's tool row measures B against A the way Realign to
Source does and swaps in B moved back over A. Save and Copy use the
lined-up B, and a second click puts B back as it came.
The new route takes only the run's pair key, reads the two previews the
node made, and writes the lined-up B beside them in the temp folder.
A list of pictures runs these nodes once per picture, and the panels
only showed one of them (#204). Arrows (2 / 5) now appear on the node
when a run brings more than one picture: Compare steps through its
pairs, A and B together, and Erase saves the pair on screen. The shared
preview panel gets the same arrows, so Save Image, Mask Refine, Mask by
Name, LaMa Inpaint and Select Frame step through their results. A node
too narrow for the arrows widens to fit while they show.
A list of pictures runs Tiled Upscale once per picture, so Stitch gets
every picture's tiles in one list and one stitcher per picture. It only
read the first stitcher and stopped with a tile count error (#205).
Each stitcher now takes its own tiles off the list and Stitch returns a
list, one finished picture each. One picture comes out as before.
Mask by Touch looks at a new picture by running itself alone. The Run
Timer started for that look and could show its time as if it were the
workflow's run, and the runtime badges were cleared by it.
The queue wrapper now marks a run that targets nodes only and carries a
Mask by Touch request as the node's own. The timer and the badges skip
those runs, and take back one that started before the queue answered.
A LoRA Loader inside a subgraph gets a first gear item, Rows on the box.
Show brings its rows up the frontend's own way (a subgraph input linked to
the loras input), and the box then shows the loader's row card on its face,
bound to the box's copy of the rows, which is what runs. Each copy of the
box keeps its own rows. On the box the card keeps the rows, the master pill
and Re-check; templates and the gear stay inside. The card also appears
when loras is brought up from the frontend's Edit subgraph widgets list.
Inside, the loader says where its rows are set and shows and edits the
copy of the box it was entered through (or the only box); with several
boxes and no known way in it is read only. Take back removes the subgraph
input and keeps the rows of that box.
Workflow Switches needs no change to set rows on a box: the box's copy is
an ordinary widget value. A row that a switch sets shows a lock in place
of its grip, naming the switch on hover; a click on the lock removes that
row from the switch's saved sides (releaseStackRow), and the row still
drags. Pure helpers live in js/shared/lora_box.mjs and
js/shared/workflow_switches.mjs, with tests.
A Text node saved in the big look between the big floor and the usual
look's floor (424 x 152) came back at the big floor (424 x 130). ComfyUI
fills in the saved text while it configures the node, before onConfigure,
and the text's callback redrew the card in the big look with the saved
size already on the node. The card took a node under the usual floor for
one sitting at it and fitted it to the new floor.
A look that changes with no hand on the card came with the node's own data
(a load, a paste, an undo), so its height change is now kept like a
restore: the node keeps its size, lifted to the floor. Picking a look from
the gear still fits the node to it.
The Text card gets a gear in its top-right corner with one setting, Look.
normal is the usual text box and stays the default. big shows the text in
large centred letters in a compact field as wide as the card, about two
lines tall, that grows with the node; longer text scrolls.
The look is kept in the node's properties (ausboss_text_look: "big"), not
in an input, so saved widget values and prompts are unchanged and an older
pack shows the usual box. The widget card gains a per-node look: rows can
carry a height and placeholder for another look, and the card height math
takes the look into account.
Picking Erase no longer grows the node, so it never covers its neighbours:
the tool rows wrap on a narrow node and the picture gives up the room.
A node that 2.12.1 grew and saved in Erase still gets its size back.
Copy draws the mix at full size in the browser and writes it to the
clipboard as a PNG; browsers allow this on localhost and over https.
The Registry shows each version's notes, made from CHANGELOG.md when the
version is published, and the full list stays in the repository; nothing
in the pack reads the file. The nodes are the same as in 2.12.1.
- Qwen Image 2.1 Inpaint example: the note and Callout name Paint (Draw before 2.12.0), need 2.12.0, and link the workflow's video
- Mask by Touch example: needs 2.12.0 for Paint and Erase
- README: section summary covers the newer nodes and no longer counts the examples; compatibility line names the nightly check; Inpaint links its video
- pyproject: Mask by Touch in the description and keywords
Erase grows a node too small for its tool rows. The size before is kept
in the node's properties, so going back to Slide, A or B returns it,
also after a reload. A side resized while erasing keeps the new size.
The node's tool row is one switch, Pick | Paint | Erase (W, B, E). Paint
adds to the mask and Erase rubs it out, exactly as drawn. Ctrl, or the
right mouse button, does the opposite while held. The brush has a size
and a soft edge, set with [ and ], Shift with them, or Alt and the right
mouse button dragged, and it shows under the pointer as it will paint.
Chips are areas: brush marks carry the number of the chip they joined
(the selected chip, else the one they touch, else the paint chip), and
erasing makes no chip. A take-away that takes nothing is not kept; the
node now says per touch whether a take-away took anything ("did"). A
thing taken away keeps its own chip. Ctrl-click un-picks a pick, a click
puts a taken-away thing back. Chips wrap onto more rows.
Soft brush marks ("s" on a paint touch) make the selection soft on both
sides: apply_item and the face's joinMasks mix by strength, the edge steps
run on soft values as the server's do, and the selection picture is grey
where a soft brush painted. The node also sends each picked thing's own
area, so the face can tell which chip a mark touches.
The look at a picture can be paused and resumed from a button under the
picture, and starts on hover also behind Reroute nodes and the pack's own
loaders.
The brush is shared: js/shared/brush.mjs and nodes/_brush_helpers.py.
Image Compare A/B's Erase mode now flips with Ctrl instead of Alt, and
takes E and B as well.
A workflow that switches Mask by Touch off, for example to find a thing by
its name instead, showed ComfyUI's red "Prompt has no outputs" message when
a new picture was loaded. The node still asked for its own look at the
picture, and ComfyUI leaves a bypassed or muted node out of every run, so
that run had nothing left to do.
- The node sends no run of its own while it is bypassed or muted, by hand,
by a Workflow Switches card, or inside a box that is.
- A look it skipped that way is made up when it is on again and the
pointer comes to its picture.
- A click while it is switched off says so on the node.
isSwitchedOff in js/shared/graph_ids.mjs answers for the node and for every
box on the way to it, with tests.
Each example's Workflow Note gets a YouTube chip first in its links and one
linked line at the top of its how-to. The note is taller by what the new
lines need; nothing else in the examples changes. The README names the
channel next to Civitai and X.
One at a time already made switching a row on switch the other rows off,
but every row kept its own off | on, so all of them could be off. For
choices such as masking by words or by a click, the card can now show its
rows as one control.
- A new card setting, Show as (rows | one pill), is there while One at a
time is on. Rows is the default and looks as before.
- As one pill, each row is a segment of the pack's segmented pill, the row
that is on is the filled one, and a click on another segment switches to
it. A click on the picked one changes nothing, so one is always picked.
- Two to four rows whose names fit are a pill, and the card's minimum width
follows the names so none is cut short. More rows, or longer names, are a
list to pick from.
- A graph with none or several rows on, or a row only partly on, is drawn
as it is, with an amber edge, and the next click leaves exactly one on.
- In the edit view the pill sits above the rows, which keep their frame
button and menu. Switch everything off is left out of the node's menu
while the card shows as one pill.
The stored settings grow by one key, show, which reads "rows" when it is
missing, so a workflow saved by an earlier version opens as it did. A click
on a choice uses the same plans as One at a time, for rows that hold nodes,
rows that change settings and rows with an off side.
With Resize on, the note that says how much the rounding stretches the
picture came and went every few pixels of a drag, and each time the picture
area and the rows under it jumped by a line.
- No note appears or changes while a pointer is down. A note the drag makes
untrue goes away, and the note for the new size comes once, 0.2 s after
the release. A click, a typed number or a new picture still shows its
note at once; a run of arrow clicks or a held arrow key shows the first
value and the last.
- The note has a line of its own that is there whenever Resize is on, with
text or empty, so a note that comes or goes moves nothing. The Even out
button no longer makes that line a pixel taller.
- The size line only grows while you drag: a step that comes and goes
("round to 8") no longer wraps it in and out.
- The full editor's size box and status panel show the same settled note,
and the status panel keeps its height mid-drag.
The image node and both video nodes share this code. Even out now works its
padding out at the click, because the note can be a moment behind the values.
The four mode buttons become one switch, Slide | A | B | Erase, whose
marker slides to the choice. In Erase the brush's two jobs and the
picture on top are switches of the same kind, and PSD is a plain
off | on.
Save moves to the bottom right of the node, larger, with a disk icon,
in a row of its own with the name of the files and the PSD choice.
The files can be named. A Name field on the node takes letters,
digits, spaces, dots, dashes and underscores; the server checks the
same rule and refuses anything else, so a folder is never part of a
name. A new optional filename input takes a wired name, such as the
source picture's file name: its folder and extension are dropped. The
save route still names no file to read.
Size and Soft scrub from their label and from the unit inside the box
as well as from the number (the scrub control gains handle() for this).
Workflows saved before load as they were: 60 that use the node were
opened (the pack's examples, published workflows, the oldest saved
shapes), 72 nodes kept their size, mode and links. The node is one
input socket taller.
A fourth mode beside SLIDE, A and B. One picture lies on top of the
other and a brush rubs the top one away, so the other shows through.
ERASE and RESTORE pick what the brush does; Alt or the right mouse
button paints back. Size and Soft set the brush, which shows around the
pointer in teal with its soft edge. It takes Photoshop's keys: [ and ]
for the size, Shift with them for the edge, and Alt with the right
mouse button dragged across for the size or up and down for the edge.
SAVE writes the mix to the output folder as compare_00001.png without
running anything again. With PSD lit it also writes a PSD with both
pictures as layers and the paint as a layer mask on the top one, and
the mask as its own PNG. After a save the row under the picture links
each file for download.
The node keeps the strokes, not a painted picture. The panel draws
them, and the save draws them again on the server at the picture's full
size. The save route takes no file name: a run remembers where its two
previews are and gives the panel a key, Save sends the key back, and
the files get a fixed name. The PSD is written by a small writer of the
pack's own, with no new dependency.
SLIDE is still what the node opens on. The node's inputs and output are
unchanged.
The second source tab was Server file, a box for a typed path. It is now
Outputs: the videos in ComfyUI's output folder, newest first, in the same
list with a preview that the Uploads tab uses. The list asks the server
each time it opens (/ausboss/transform/video/outputs, no parameters), so a
video rendered a moment ago is in it.
The picked video is saved the way ComfyUI names a file in one of its
folders ("clip.mp4 [output]"). resolve_server_file reads that form and
nothing else: a whole path, a drive, "~", ".." and a network path are
refused from the text alone, and a link that leads out is refused once the
path is resolved. A workflow saved with a typed path shows nothing picked
and a line that asks for the pick.
Video Crop + Rotate + Pad (Frame and Clip): Server file mode read a whole
path that had to lie inside ComfyUI's input, output or temp folder. It now
takes the folder and the name (output/clip.mp4) and joins them below that
folder. A whole path, a drive, "~", ".." and a network path are refused
from the text alone, and a link that leads out is refused once the path is
resolved. The hint under the field says what to type when a whole path is
in it.
LoRA Loader: the preview route sends a small JPEG made from the picture
beside the LoRA instead of the file itself.
resolve_input_path refuses when ComfyUI's folders are missing instead of
opening the name unchecked. A route's error text that names a place on the
server's disk is replaced whole (nodes/_route_errors.py), on the LoRA
routes too.
ComfyUI calls a media input missing when its value is not in the
widget's list. The MaskEditor writes the file it saved into the image
widget, and no list has that file until the lists are refreshed, so
the node was flagged the next time it came out of bypass. Both nodes
now add a picture to their list once it has loaded. Load Image + Pad
also follows a picture written that way, as the crop node does.
A row holding a Draw Things copy of a LoRA, a web page saved by a failed download or an empty file stopped the run with PyTorch's weights_only message, which never says which file is wrong. The node now reads the first bytes of each row's file before the run, names the file, says what it is and what to pick, and a file that fails while loading gets the same message with the loader's error at the end.
While the node was still looking at a picture, a thing could light up
under the pointer and a click on it only left a dot. The click was held
back unless the node was already sure of the thing's name. Three clicks
left three dots, and when the look was done they became three picks at
once: the shirt, the person and one more.
- What lights up is what a click takes, also while the node still looks:
a click on a lit thing takes that thing at once, whatever the node knows
about its name.
- The picture says "Looking at the picture…" in its corner, with how far
along it is ("40 of 104"), for as long as the look is not through.
- A click where nothing is lit yet still waits for the look, as before,
but one click at a time: the picture says "Your click is in", and more
such clicks are answered by that message instead of piling up. A click
on a lit thing does not rush the one that waits, and answers it when it
is at the same spot.
A click is never kept waiting longer than 45 seconds (was 12): a first
look that also loads SAM 3 takes about 25.
At some node widths and zoom levels a line of the picture that was not on
show ran down the right edge of the one that was. It stood out when one
side was a padded canvas: its grey showed beside the result. Only the
node's view had it, never the saved picture.
The node drew picture B under picture A, and A carried a clip even when
it was shown whole. A browser draws a picture on whole pixels but cuts a
clip where it falls, so on a stage whose width is not a whole number the
clipped A stopped up to a pixel short of B, and B showed as a line.
A picture that is wholly covered is no longer drawn, and a picture shown
whole has no clip.
Click a thing in your picture on Mask by Touch, say what to change, and
Qwen repaints only that part. It is the workflow of its page, with the
Seed on fixed as every example here has it.
The workflow carries a note above its Workflow Note that links to the
video channel. The example check wanted every node but the Workflow Note
inside a numbered group, so that note counted as a stray node. A Callout
in the Workflow Note's own column now belongs to the note. A Callout
anywhere else outside the groups, and any other node in that column, is
still refused.
Some LoRAs list a whole sentence as their trigger. A click on it in a
row's info card added it to the row, but its chip stayed dark, and a
second click added it again instead of taking it out.
A row keeps its triggers as one comma-joined text. The chip's check and
toggleTrigger both split that text at commas and looked for the whole
trigger as one part, so a trigger with a comma of its own never matched.
A trigger is now looked for as the run of parts it becomes (hasTrigger),
and toggleTrigger uses the same search. Off takes out every copy, so a row
that already holds the sentence twice is cleared with one click. A click
refreshes every chip of the card: the same words can sit in two lists.
No Python change and no input change. The row text and the triggers
output are what they were for every trigger.
A changelog entry that opens with a node's name and a colon, as in
"Callout: links", came out as "Callout : links" in the notes sent to the
Registry: the mark after the name was taken off and its space stayed.
The Registry has shown no notes for any version since 1.2.0, when
publishing moved to the publish action: it sends none. comfy-cli takes
them from COMFY_NODE_CHANGELOG, so the publish workflow now puts this
version's CHANGELOG entries there before it publishes, one short line
each: the entry's bold opening, and for a new node or example the sentence
that says what it does.
scripts/changelog_notes.py makes the notes and prints them for any
version. The release check fails when the pyproject version has no
entries in CHANGELOG.md, so a release cannot go out blank.
Choose a picture and the node looks at it for a few seconds. Choose
another one in that time and the node finished the first look before it
started on the second, so the new picture sat there while the old one was
still being worked on.
A look gives way to the next job in ComfyUI's queue. The new picture's
run is now sent at once, behind the look that is on its way, which ends
that look within a moment. Its answer is dropped: it is about a picture
the node no longer has.
- Image Crop + Rotate + Pad and Load Image + Pad: the look at the new
picture no longer waits for the old one to be over.
- A plain Load Image: the same, for a look that was on its way when
another file was chosen.
- A second look at the same picture still waits for the first, and
nothing goes behind a run that is no look.
Also: a plain Load Image that had no file when the workflow was opened
was not watched, so the first file chosen on it did not show on the node
until something else made it look again. It is watched now.
With Resize on, Image Crop + Rotate + Pad and both Video Crop + Rotate +
Pad nodes resized the padded canvas as one picture. Along every seam that
mixed fill into the picture and left the mask part on, part off. A hard
paste (stitch blend 0) kept that column as a faint line, and a band under a
pixel wide after the resize came out as nothing but that column: 1 px of
padding at a 0.62 scale left a line in the stitched result whatever the
model painted.
resize_then_pad (nodes/_transform_engine.py) resizes only the picture, with
the chosen method, into the box it has on the resized canvas, and puts the
fill around it. Bands are whole pixels, the picture's edge is picture, and
a band that comes to under half a pixel is left out. The mask is 1 on the
fill; inside the box it is the transform's own mask there (the feather's
ramp, the corners of a turn, see-through parts), resized bilinear with the
picture. The prompt view is placed the same way.
The box is transform_picture_box (nodes/_inpaint_crop_helpers.py), which
the stitcher now uses for its source bbox too, so the paste and the bbox
cannot disagree. It is never empty.
The output size is unchanged. With Resize off, or with no padding, image
and mask are bit for bit what they were. With padding, the picture can sit
up to half a pixel from where it did.
Image Crop + Rotate + Pad and both Video Crop + Rotate + Pad nodes rounded
the canvas up to Divisible by and put the pixels left over on the right and
bottom, whatever was padded. A 1916 x 821 picture with 3 px of padding on
the left and Divisible by 24 came to 1920 wide, with 1 px of fill on the
right edge, which had no padding. With Resize on, that strip was smeared
into the picture's last column, and the stitched result kept it as a light
line.
The canvas now rounds one axis at a time by Load Image + Pad's rule
(_pad_helpers._axis_layout): the leftover joins a side that was padded, the
right or bottom one when both are, and an axis nobody padded has its crop
trimmed evenly to the multiple below. A picture smaller than one multiple
still grows on the far side. With no padding on an axis the size therefore
rounds down, not up: 1000 x 750 at 64 gives 960 x 704, was 1024 x 768.
Inputs and outputs are unchanged.
The editor uses the same rule (axisLayout, resolvePadding and keptCrop in
js/shared/transform_geometry.mjs), so the picture on the node, the size
line and the editor match the run. The size line names fill added on any
side and pixels trimmed, and the centre buttons count fill that sits on the
left or top. The Divisible by tooltip and help pages describe the rule, and
the Krea 2 Outpaint Model Patch warning no longer says Divisible by rounds
the other axis up.
tests/test_transform_engine.py and tests/transform_geometry.test.mjs share
one fixture table, so the editor and the run are held to the same numbers.
With a plain Load Image wired straight in, the picture shows on the node
as soon as a file is chosen. With Image Crop + Rotate + Pad or Load
Image + Pad it did not: the node showed Show picture and waited for a
press, because those loaders may crop, turn or pad the file, so the file
alone is not the picture.
The node now asks for that run itself. A moment after a file is chosen on
one of those loaders, a setting on it is changed or the wire is made, the
loader's picture is on the node, cropped and all.
- Changes are waited out for 0.7 seconds, so a crop being dragged sends
one run when it rests, not one per step.
- Only in answer to something the person did: opening a workflow sends
nothing, and picks saved with a workflow still wait for their own
press.
- Nothing is sent without SAM 3 wired, or with looks set to "only on a
click or the Look button".
The standard-library run has no torch, and the new test imported it. It
now reads a stand-in with a tensor's size, kind and value; the same case
on a real tensor runs where torch is installed.
A row only turned its own nodes off and on. A choice that means "use this
and leave that out" needed two rows and two clicks, and the two could be
set against each other. An edit, say, takes its size from its picture, so
the node that sets the size has to sit out while the picture loader runs.
A made switch can now have an off side: nodes and groups that run only
while the row is off. Switching the row on turns them off, switching it
off turns them on, in the same click and the same undo step as the row's
own nodes.
- The row's menu gets "The selected run only while this is off" and "Take
the selected off that side". A node is on one side only: putting it on
one side takes it off the other.
- A row with an off side shows a small sign of two arrows in the edit
view; its tooltip names the nodes. The frame button and "Select its
nodes" take in both sides.
- The row reads on when its own nodes run and its off side does not, off
when it is the reverse, and mixed otherwise.
- One at a time and "Switch everything on / off" follow each row's off
side.
Stored as nodes_off and groups_off beside nodes and groups, and left out
while empty, so a switch that does not use it is saved exactly as before.
A note could point at a node with a linked arrow, and that was all. A note
that wants to send people somewhere else, to a video or a model page, could
only tell them where to look.
Words can now be a link, written [the words](target):
- an http or https address opens in a new tab;
- #12 brings node 12 into view, the way a linked arrow does;
- #g3 brings group 3 into view, and an arrow can point at a group too.
A note that is nothing but one link is a big button for it: its words fill
the note.
In the editor the new Link button wraps the selected words, or the word
under the caret. With a node or a group clicked on the canvas first, the
words point at it; otherwise the address comes back selected, ready to be
pasted over. An address pasted over selected words makes them a link.
Only http and https addresses become links; anything else in the round
brackets leaves the text as typed. A link is a real <a> with
rel="noopener noreferrer", it is only ever opened by a click, a press on it
never drags the note, and a double-click is one visit. An older version of
the pack shows a link as typed, so the address can still be read there.
Picking a group as an arrow's target used to write the group's number as
if it were a node's; it now writes #g and the number.
Help page, tests and CHANGELOG entry included.
Found in a sweep of the pack against ComfyUI's own nodes: things the normal
node does that the pack's node did not.
- Every number box takes a sum. Click a number, type 1024*1.5 or
(1920-64)/2 and press Enter, as in ComfyUI's own number boxes: + - * / %,
brackets and a sign. The reader is the pack's own (sumValue in
js/shared/scrub_input.mjs) and is only asked when the text is not a plain
number.
- Show Text takes any wire. Its input was STRING only. A number, an on/off
value or a list is shown too; a picture or a latent is shown by its size.
The output stays STRING and carries the text that is shown, so text
passes through unchanged and a number comes out as text.
- Save Image takes name tokens in the file name, as ComfyUI's own Save
Image does: %date:yyyy-MM-dd% and %Node.setting% are filled in the
browser when a run is sent (js/shared/name_tokens.mjs, now shared with
Save Video), %width% %height% and the time tokens on the server. The
path preview shows them. A linked filename is never read for tokens.
- Ctrl + Up and Ctrl + Down change a word's weight in the pack's prompt
boxes (Text, Krea 2 Encode). The card's text box stopped the key before
ComfyUI's own listener saw it; those two keys now go on from a multiline
box, and from nowhere else.
- Image Size shows what it measured beside its outputs after a run
("width: 1024"). The numbers go into the output labels, so the node keeps
its size. ComfyUI says nothing about a node whose answer came from its
cache, so the numbers are kept in the node's properties and come back
when a workflow is opened again; a changed picture wire clears them.
No input was added, renamed or reordered. Help pages, README lines, tests
and CHANGELOG entries are included.
A Callout's text is fitted as large as the note allows, so a note with a
headline and its details ended up with all of it at one middling size. A
line can now be small print: it is drawn small under the line above it, the
first line stays big enough to read from far out, and the note holds more.
In the editor the new Small button marks the caret's line, or every line of
a selection, and takes the mark off again. In the text it is a line that
starts with six hashes and a space.
Why six hashes: every release so far already drops one to six leading
hashes from a line, so a note with small print opened on an older version
of the pack shows the same words with every line at one size, and no note
written before changes its look unless it used that exact mark. A dash was
the first idea, but a dash line is an ordinary line today and dash lists
would have shrunk.
The editor's bar was already squeezed on a narrow note. With one more
button it now drops its label and its hint under 345 px, and goes onto two
rows when the note is tall enough, so the buttons keep their size.
tests/fixtures/callout_released_notes.json holds what release 2.10.1 made
of 14 notes (dash lists, every heading mark, the example notes); the new
parser has to give the same. Help page and CHANGELOG entry included.
In the Crop + Rotate + Pad editor, Show blend could draw a teal ramp along
the bottom or the right edge of a picture where there was no padding. The
run never blended there: only the preview was wrong. A 1916 x 821 picture
with 100 px of padding on top showed it along the bottom.
The preview drew the generated area on a small raster with one scale for
both axes, while the raster's size was rounded per axis. 921 rows came to
184.58 raster rows on a raster 185 tall, so the last row was only part
covered by the picture and read as generated, and the blend grew that row
into a ramp.
generatedAreaMask (js/shared/stitch_preview.mjs) now works the area out:
the turned picture cut to the crop, mapped onto the raster with each axis
by its own scale, each cell's share measured. An edge of the picture on the
canvas edge is exactly 0, also after a quarter turn. See-through parts are
still drawn from the picture, on the same raster. Feather, grow and blend
are unchanged, and so are the ramps at real seams.
tests/fixtures/show_blend_canvas.json holds the run's blend for eight
settings at the preview's size. tests/test_show_blend_fixture.py holds it
to the run and tests/stitch_preview.test.mjs holds the preview to it.
A Float wired to a sampler's denoise could be set above 1, where ComfyUI's
own Primitive node stops at 1. Both number boxes now follow their output to
every input it reaches, through Reroutes and into subgraphs, and keep to the
range all of those inputs allow: by typing, by the arrows and by a drag. A
value that is outside when the wire is made comes to the nearest limit, the
card shows the limits beside the value (0–1), and an unwired box takes any
number as before. No input was added, so saved workflows and API requests
are unchanged.
With it:
- A new box takes the value of the first input it is wired to, as the
Primitive does. A box that came with a workflow or a paste, or one that
was already set, keeps its own value.
- Float shows the decimals its steps reach (1.00 wired to denoise, 8.0
wired to CFG) and goes past them only for a value that uses more. It read
1.000 everywhere before.
- Integer's own limit goes from 2,147,483,647 to 9,007,199,254,740,991, the
largest whole number a browser holds exactly, so a seed fits.
The walk over the graph is js/shared/wired_limits.mjs; the scrub control
gains setRange and a way to drop the zeroes its steps never reach. Help
pages, tests and the CHANGELOG entries are included.
- + LoRA opens one list grouped by folder. A click on a file adds it, a
click on a folder name adds every LoRA under it. Files already in the
stack are skipped, the add is one undo step, and nothing is added when
it would pass 64 rows. Folders fold with their own arrows; the folded
view is remembered in the browser, and search shows matches without
changing it.
- A row can be reached with Tab and worked with the keys: Up/Down between
rows and + LoRA, Enter for the file picker, Space for the switch,
Left/Right for strength (Shift for the fine step). Delete and Backspace
are kept from ComfyUI there, so they cannot delete the node. Focus goes
back to a row only when the keyboard was in use; after a mouse click
the keys stay with ComfyUI as before.
- Remove this LoRA is red in the row menu.
- The row switch reports on/off to screen readers and the strength boxes
have short names.
The nightly ComfyUI job is the first place these two files ran with torch on
a GitHub-hosted runner, and one runner failed both on unchanged code.
- test_memory_helpers.py imported torch four times in one process:
patch.dict(sys.modules) forgets whatever was first imported inside it. The
fourth import ended the process without a word. Torch is now loaded before
any test swaps modules.
- test_stitch_classic_pins.py compared blend masks by hash. The feather's
blur takes an exp, which CPUs round differently, so two masks moved by a
float32 step. Masks are now compared with frozen release masks at the
tolerance the images already have, and a new test holds that file to the
pinned hashes.
The nightly job also prints which CPU ran it.
A ComfyUI update can break a node without a line of the pack changing, and
until now nothing looked: CI installs no ComfyUI. A nightly workflow now
installs ComfyUI's newest release and its development head for the CPU and
runs three checks that need no model:
- tests/test_comfy_core_contract.py reads the pack's source for everything
it takes from ComfyUI (modules, functions, ComfyUI's own nodes it calls
and the inputs it passes them) and looks each one up in the real thing.
- scripts/compat_boot.py asks a running ComfyUI which nodes it offers:
every public node of the pack, and every node and required input the
example workflows use.
- scripts/dev/face_smoke.mjs puts every node on a graph in headless
Chrome, in both node views, and fails when a face does not come up or
something throws.
The full Python suite runs there too. A failure on the release opens an
issue; the development head only after two nights in a row.
A new node in the Mask group. The picture is on the node: moving over it
lights up what a click would take and names it, a click picks it, and two
arrows swap the pick for the next thing at that spot. Subject and
Background take the picture's main subject, or everything else, in one
click. Pick | Draw switches to a brush for an area painted by hand, + and -
add or take away, Undo takes a pick back, and every pick has a chip under
the picture. Fine edges follows hair and fur strand by strand, takes out
background seen between the strands, and gives a cut-out the thing's own
colour. The eye beside Undo and Clear shows the result as a cut-out or as
the mask. Outputs: the mask, the cut-out and the names of what was picked.
The search is ComfyUI's own SAM 3. Subject, Background and Fine edges use
ComfyUI's own background-removal model (ComfyUI 0.21 or newer). The pack
downloads nothing.
With it: a widget card can name its choices with a function and keeps
showing a value its list does not offer, the help page, an example
workflow with its thumbnail, the README section and the CHANGELOG entry.
The mouse wheel zooms and the middle button pans the graph from every part
of a node in the classic node view. The version pair and the changelog title
follow.
The wheel did nothing over a preview picture, the text of a Callout or the
padding of a card, and the middle button did not pan there. Those parts of a
panel let the mouse through, so in the classic renderer the pointer lands on
the frontend's .dom-widget wrapper, which takes the mouse and hands nothing
on. The pack looked for its panel between the pointer and the wrapper and
found none. A pointer on the wrapper now counts as over the panel inside it.
scripts/dev/wheel_sweep.mjs turns the wheel at every point of every node and
reports each spot where the graph does not zoom. In the classic renderer 33
of the 41 nodes had such spots; none has one now. Nodes 2.0 had none.
With a ComfyUI checkout set (AUSBOSS_COMFY_ROOT) and no GPU visible,
test_video_clip_transform, test_video_load_helpers and
test_video_stream_start failed with "No CUDA GPUs are available". The
decode loop checks for a cancel through comfy.model_management, and that
module asks for the GPU as it is imported.
core_video_api_available() now puts ComfyUI in CPU mode before it
imports comfy_api, as the node API test already does. These tests need
no GPU, so the whole suite passes with CUDA_VISIBLE_DEVICES="".
The Painted area switch on Image Crop + Rotate + Pad, Image Compare A/B
resting on the result, Workflow Switches finding a LoRA row by its file
name, the Denoise box in the Krea 2 Inpaint Masked example and Tiled
Upscale's report wording. The version pair and the changelog title follow.
A switch that turns a LoRA Loader row on and off looked the row up by its
full saved name. The LoRA Loader gives a row the folder its file is in when
Refresh or its re-check button finds the file moved, so after that the switch
no longer found the row: it left it as it was and the card read "changed".
The row is now matched by exact name first, then as the one row with that
file name (no folder, any letter case, any model extension), the way the
loader resolves names. Two rows with one file name are not guessed at.
A part painted in the MaskEditor (see-through in the saved file) was
always handed on as flat fill. The new optional painted_area input,
"fill" by default, can be set to "keep picture": the mask is unchanged
and the image, original and prompt_image outputs carry the stored
picture there. The editor's Inpaint & Stitch section has the switch.
The sampler started from the gray-filled crop, so a denoise under about
0.85 left a flat gray patch. It now starts from the plain crop; the
gray-filled copy stays the reference. A Float - Denoise box is wired to
the sampler. At denoise 1 the prompt differs only in that wire.
With the slider on, the panel stayed on whichever picture the pointer
last left over, and a new run did not change it, so a result could
finish behind the picture it was made from.
The panel now shows the result after every run and each time the
pointer leaves. The result is image_b, or image_a when the node feeding
image_b is upstream of the node feeding image_a; when both come out of
one subgraph the same check is made inside it. The A and B buttons
still hold one picture.
Settings > AusBoss > Image Compare > Rest on the result switches the
earlier behaviour back on.
Asking for fewer megapixels than the picture has got the "too close to
the picture's own size, ask for more" line, which is wrong for a shrink.
The report now says the picture is made smaller first. The help page says
when that is the right thing to do: a big photo that is noisy or soft.
Tiled Upscale and Tiled Upscale Stitch, with the Qwen Image 2.1 Upscale +
Restore example, and the Nodes 2.0 fix for Callout. The version pair, the
changelog title, the description and the keywords follow.
The example's controls now run in the order people use them: Megapixels,
one note about it and the Fast switch, the switch with its steps, then the
request and the seed. The picture's note says to load a picture first.
Same nodes, values and wires.
Two Image nodes that take a picture past the size a model can redraw in
one go. Tiled Upscale sets the finished size in megapixels and cuts the
picture into tiles; Tiled Upscale Stitch blends the joins, keeps each
tile's colors on the picture's own and fits the result to the size asked
for. Any model can sit between the two.
Adds the tile planner with its tests, the two widget cards, help pages,
README sections, a changelog entry, the API snapshot and a new example,
Qwen Image 2.1 Upscale + Restore. The README's example count now matches
the folder.
Nodes 2.0 sizes a node by its content, and the note's text was in normal
flow inside its card. So the text set the note's height: a note could not
be made shorter than its own text, and the text was fitted to a card it had
sized itself. A note made in the classic look and saved 888 x 122 was drawn
20 px taller after a switch to Nodes 2.0, a note dragged taller would not
come back down, and one dragged narrow grew to about 690 px.
The text is now absolutely positioned in the card and centred by its auto
margins, so the card takes its size from the node alone in both renderers.
The classic look is unchanged: same sizes, same pixels.
tests/panel_guards.test.mjs holds the rule for everything the note puts in
its card.
The 2.8.0 upload was refused with "description is too long": naming
Image Folder and Callout took the description to 1024 bytes and the
Registry takes 1000. It is 989 now, and release_preflight.py fails on a
description over the limit, so this is caught before a version lands.
The Registry's scan also warned about two statements on one line in
tests/test_image_folder.py and says that becomes an error. They are two
lines now.
Callout and Image Folder, the Nodes 2.0 size fixes, and Save Video
showing a saved gif or webp. The version pair, the changelog title, the
description and the keywords follow.
A new Utility node for explaining a workflow on the canvas. The text grows
to the largest size that fits the note, so it reads without zooming in, and
each point gets a thin teal line beside it.
An arrow emoji in the text (or the arrow row under the editor) becomes a
teal arrow in the line. Click a node while editing, then press an arrow, and
the arrow is linked to that node: clicking it later brings the node into
view, the way Workflow Switches does. The link is kept in the text as #id,
so editing the words never moves it.
The node has one hidden text input and no outputs, so it never runs.
The row next to Fill, Feather and Resize said Centre. It now says Center,
and so do the two button tooltips and the three crop help pages. Code
names and comments are unchanged, since nobody sees them.
With format set to gif or webp the file was saved, but the node's stage
stayed empty and said "Execution finished without video metadata".
The node has shown a gif or webp in an <img> since the two formats were
added, because a <video> cannot play either. The run's result was still
searched with a list of video file endings only, so for these two nothing
was found and the <img> was never filled. The list of picture endings now
sits beside that lookup in js/shared/video_preview.mjs, and both the
lookup and the choice of tag read it. A png is still not taken.
In the classic renderer a saved webp was also drawn a second time under
the stage. ComfyUI shows an animated webp in a widget of its own, neither
its video preview nor its picture preview, and only those two were stood
down. suppressCoreVideoPreview hides that widget as well. The node's
smallest height with a saved webp goes from 469 px back to 415 px.
Tests: the lookup finds a saved gif and webp and sends them to the <img>;
every row of the backend's VIDEO_FORMATS is read from its source and has
to be found and sent to the right tag; the animated widget is hidden on
arrival and when it is already on the node.
In Nodes 2.0, Load Video and Save Video could not be dragged shorter than
their clip drawn at the node's full width. With a 9:16 clip, Load Video at
320 px wide stopped at 965 px, where the classic renderer goes to 584 px,
and Save Video at 823 px against 446 px. A node saved at its classic size
was drawn taller than saved and could cover the node under it.
Nodes 2.0 lays a node out by its content, and the saved size is only a
minimum. The shared stage kept its <video> in normal flow at full width, so
a portrait clip set the stage's height. The clip now sits out of flow and
fills the stage, and so does the <img> Save Video keeps for a gif or webp.
The stage takes its size from the node alone and the clip scales to fit.
The player fills the stage, where the preview picture is centred at its
own size, so its controls stay along the stage's bottom edge.
The classic renderer is unchanged.
A new node opens at its smallest size plus one more row of tiles
(340 x 602). That size was set a moment after the node was made, on any
node that sat at its smallest height. A workflow that loads has put the
saved size on the node by then, so a node saved at its smallest size
looked new and was grown: saved 320 x 506 came back 340 x 602, and saved
400 x 506 came back 400 x 602. A copy, a paste, an undo and Recreate node
grew the node the same way. A node saved 2 px taller kept its size.
The opening size is now set as the node joins the graph. The card is in
place by then, and a size put on the node afterwards wins: a loaded
workflow, an undo, Recreate node. A copy gets its size before it joins
the graph, so a node that is configured with a size is marked and left
alone. A node saved under its smallest size is lifted to it, and one
saved with no size opens like a new node, as before.
tests/image_folder.test.mjs fails when the opening size is set anywhere
but as the node joins the graph, or when a node that comes with a size is
no longer marked.
Choosing a folder in the file explorer makes the browser ask its own
question ("Upload 74 files to this site?"). A page can neither word that
question nor skip it, and it does not say where the files go.
- While the file explorer is open, the node shows a note: all the folder's
pictures are copied into your ComfyUI input folder, and the browser's
upload question is this copy.
- While it copies, the node names the folder and where its pictures go,
and how far the copy is.
- The big-folder question, the message afterwards and the Browse and
empty-gallery entries say "copy" too.
- Help page: what the browser's question means, and that a dropped folder
is copied without it.
In Nodes 2.0 a node with a picture panel could be dragged shorter than the
panel's minimum. A corner drag took Mask Refine from 356 to 218 px and left
its picture a 2 px line, Save Image lost its saved picture, and Latent Size
lost its whole stage. The classic renderer stops at the panel's floor.
The classic floor is the minHeight a panel declares through fillNodeHeight.
Nodes 2.0 lays a node out by its content and does not use that number, so a
panel whose content has no height of its own had nothing to stop at.
fillNodeHeight now also writes the floor, less the DOM widget frame, to the
panel's element as a CSS min-height while the element sits inside a Nodes
2.0 node. It is written whenever the floor is read and when the panel is
first laid out, so a floor that follows state is followed: with the preview
switched off the node still shrinks to the bar.
The preview panel's stage also carries its own floor inside a Nodes 2.0
node. That panel's declared floor holds less frame than the shared number
takes off, and without the rule its nodes stopped 10 px under the height
ComfyUI lifts them to on the next load.
The classic renderer is unchanged.
A folder dropped on the node, or chosen in the file explorer from Browse or
Add, is copied into ComfyUI's input folder by the browser through ComfyUI's
own upload, and the node moves to the copy. A folder with more than 300
pictures or more than 1 GB asks first. Bringing the same folder in again
writes over its copies.
A whole path pasted into Folder is read as the folder it names when it lies
inside ComfyUI's input or output folder. Any other path is still refused,
and the node now says what to do instead, with a button that opens the file
explorer. The server reads nothing outside those two folders.
In Nodes 2.0 the node showed its source picture twice: on its pad canvas,
and again underneath as ComfyUI's own copy of the node's images. The copy
took the node's spare height: a node 900 px tall gave the pad canvas
278 px, where the classic renderer gives it 512. With a picture wired in,
the copy showed the file named in Source, which the node was not using.
The node's own suppression removed the classic preview widget and blocked
node.imgs, but never set hideOutputImages, the flag Nodes 2.0 reads before
it draws a node's images under the widgets. It sets the flag now. The
classic renderer never reads it.
tests/core_preview.test.mjs scans every frontend file that stands the
classic preview down by hand and fails when one does not stand the
Nodes 2.0 copy down as well.
Both are cards alone on their node. In Nodes 2.0 they stretched with a
node made taller, with empty space inside the card. They call
pinVuePanelHeight like the other cards now. Run Timer stays the one panel
that fills its node on purpose, and the guard names only it.
A loader for many pictures. The card chooses the folder inside ComfyUI's
input or output folder, and a thumbnail gallery under it ticks the pictures
to use. All in one run sends every picked picture through as a list; one
per run loads one picture each run and moves on when a run is queued. It
gives image, mask, filename, index and count. Pictures dropped on the node
or added with Add are uploaded into the input folder.
Folder names and picked names are attacker-controlled: a folder is only
read as a path below the input or output folder, links that lead out are
refused, and picked names are compared with what the folder holds, never
opened.
In Nodes 2.0 a node with a card above a picture panel gave half of its
spare height to the card. The card got empty space under its last row and
the picture was drawn at about half the size it has in the classic
renderer, on a node at its smallest size too.
Nodes 2.0 lets a widget's row take spare height whenever the widget has a
computeLayoutSize, and every DOM widget inherits one. A card that pinned
only its computeSize was pinned for the classic renderer alone.
pinVuePanelHeight clears the inherited method, and the widget cards and
Save Image's card call it. The classic renderer is unchanged.
Tooltips, two labels and one console warning still described older
versions of the nodes. Wording only: no inputs, outputs or saved values
change.
- Latent Size: the gear, its menu and an error said "Resolution".
- Video Crop + Rotate + Pad -> Clip: tooltips said Snap and Limit where
the node says Frames for and Length, and described dragging a handle
to move the whole window. IN and OUT trim; Length sets a frame count.
- Run Timer: earlier runs are in the right-click menu, not under the
number.
- Krea 2 Outpaint Model Patch: the rounding strip the warning names only
comes from Divisible by on the Crop + Rotate + Pad nodes. Load Image +
Pad never adds one.
- Save Image: its preview switch said off skips a temp file. It writes
none, so it gets its own tooltip; the other preview nodes keep theirs.
- Workflow Note: the Banner layout shows the title, subtitle and author.
- LoRA Loader: a docstring said the pack only reads the Civitai file. It
also writes it on Fetch Civitai info.
32 of the ? help pages checked against the code and corrected.
Wrong claims fixed: Stitch Inpaint's empty-area check (20/255), what a
new video keeps on both Video Crop + Rotate + Pad pages, Load Video's
frame picker and player height, where Run Timer lists earlier runs,
inpainting on the Krea 2 Outpaint Model Patch page, Workflow Note's
banner layout and pack detection, Image Resize's pad bars and mask,
Save Image's path preview and tags, Free Memory's steps, and Latent
Size's sockets.
Image Crop + Rotate + Pad now follows the node from top to bottom; the
saved values no row shows moved under Technical details.
Controls are named the way the node shows them, with the input name
beside. Pages that opened with jargon now open with what the node does
and when to use it. LoRA Loader gained the Civitai lookup, suggested
strength and the gear options it did not name.
A width+height box with a 0 in it takes its shape from the source, so only rounding and Multiple separate the two. Pad answered that with a bar 1 or 2 px wide: a 1000x700 picture with Multiple 16 came out 1008x704 with a 2 px bar. The picture now fills the box. The output size is unchanged, and a box with both sides set pads as before.
Picking a frame writes IN. With the frame at or after OUT the node held a start after its end, and the next run stopped with "Load Video needs start_seconds smaller than end_seconds" until a handle was moved.
Mask by Name and Workflow Switches, with an example workflow that shows
both. The version pair, the changelog title, the description and the
keywords follow.
The card read a row's hover text from the widget, where the frontend does
not keep it, so no row of any card said anything on hover. It now falls
back to the node's definition, which holds the tooltips the backend
declares.
A card whose height changed set its node to its smallest size, so a node
made taller for its picture lost that height whenever More opened or
closed. The node now grows or shrinks by what the card did when the person
changed it by hand, stays as it is when the card changed by itself, and a
loading workflow takes its saved height. A node that sat at its floor
still hugs the new floor.
Fill holes fills gaps that lie fully inside the mask, with the same fill
Mask Refine uses. Free VRAM takes SAM 3 and its text reader out of the
graphics card's memory right after the search, with ComfyUI's own unload
of one model, also when the search found nothing. Both are off by
default.
Registers the node, gives it a widget card and the shared preview panel
with a FIND tool that runs the node alone, a readout of how many things
matched and how sure SAM 3 was, and the stop message on the node's own
face. A stopped run shows the plain picture, not the last find. Each name
asks SAM 3 for up to 16 matches, and Sureness starts at 0.7.
The card took any whole number with a range over 2048 for a pixel size and
stepped it by 8. On the Integer node that made a Steps box go 25, 17, 9, 1:
8 was out of reach without typing. Select Every Nth, Split Batch, Select
Frame and Load Video's frame limit stepped by 8 too.
A whole number now moves by its own increment. A row steps by 8 only when it
is a pixel size (a px unit, or `pixels` on a row that shows no unit, as
Image Resize's W x H), and a row can name its own `step` (Load Image + Pad's
Multiple keeps 8).
Each example keeps its inner steps in two boxes, and nothing on the canvas
said what was in them. A note now sits at the top of "Behind the scenes",
right above the boxes. It says what they do in one sentence, then
"Double-click one to see inside." The text is a heading, so it reads at
the zoom the examples open at.
The boxes and the Models group under them move down 158 px to make room.
In Krea 2 Inpaint Masked and Qwen Image 2.1 Edit + Realign the picture
nodes beside them are that much taller, so every group still ends level,
and the saved view is re-centred (Qwen opens at zoom 0.7, was 0.72) to keep
the whole top level in sight. In the watermark example the group grows
upward instead, where there was free canvas, so its boxes and wires stay
put. The Workflow Note card loses its "The two boxes in the middle do the
work" line and is that much shorter.
What each workflow sends to the server is unchanged apart from the
Workflow Note text. Checked in the classic view and in Nodes 2.0: no
overlaps, nothing outside its group, every note fits its text.
Every node stops at the width it was built for when it is dragged narrower:
the growing panels and the fixed cards. Load Video's IN and OUT boxes stay
whole in a narrow node.
The frontend adds 104 pixels to a growing panel's minimum width and leaves a
fixed-height card's minimum out of its width sum, so a card fell to a 210
pixel default. Panels pass exactMinWidth to fillNodeHeight or call
holdNodeMinWidth, and tests/panel_guards.test.mjs fails a new panel that does
neither.
Qwen Image 2.1's VAE decodes RGBA, so wiring its VAE Decode straight into
Stitch Inpaint failed with "Inpainted channels (4) do not match the cropped
image (3)". A 4-channel patch over a 3-channel stitcher now loses its alpha;
every other channel mismatch still raises.
A LoRA from the note's list that sits in a subfolder only showed as
installed, while the model, text encoder and VAE lines offered Use it. The
LoRA line now shows the folder and offers Use it as well, which writes the
found path into the LoRA Loader row. LoRA Loader follows a row change made
by another node in Nodes 2.0 too.
Use it also wrote the found path with forward slashes. A Windows server
lists files in subfolders with backslashes, and a loader only takes a file
exactly as listed, so the loader kept an unknown value. Use it now writes
the folder's own spelling.
2.6.2 let app shortcuts through from the boxes on a node face. Six more
boxes still stopped every key: the LoRA picker's search box, the trigger
word and template name boxes, the gear menu's boxes, the picture and video
picker's filter, and the server file path box on the video nodes. They use
keepKeyInField now, and a search box no longer treats Ctrl + Enter as a pick.
A test reads every keydown handler under js/ and fails on one that stops a
key before looking at it. The Workflow Note editor and the keep or discard
question are the two that own the keyboard on purpose.
Every field on a node face stopped all key presses, so ComfyUI's shortcuts did nothing while the cursor was in a Text node, a number box, the Seed box or a file name box. Ctrl + Enter only queued a run after a click outside the node.
The fields now keep the keys they type and edit with (so Delete or Ctrl + A never reach the canvas) and pass app shortcuts on to ComfyUI, as its own text boxes do. One helper, keepKeyInField in js/shared/canvas_passthrough.mjs, is used by the shared card's text, multiline and colour fields, the number boxes, the Seed box, Save Image's name fields, Load Video's IN and OUT boxes and LoRA Loader's strength boxes.
Updating the pack in Manager restarts ComfyUI and reloads the workflow, but
not the page. The tab kept running the old AusBoss scripts and nothing said
so: the update guard only looked once, when the page loaded.
The guard now looks again every time the server comes back, asks once per
installed version, and its message stays until it is closed instead of
going away after 30 seconds.
Follow-up to issue #108. The missing LoRA Loader panel reported there was
old browser copies of the shared modules, fixed in 2.5.2.
In Nodes 2.0 a panel sits in the node's own widget area instead of a
.dom-widget, so the pass-through did not recognise it and the drag-zoom
never started there. Both layouts are matched now.
The pack's server still never contacts a host. The info card's button has the
browser ask civitai.com about one file by its SHA256, then the server saves
the answer beside the LoRA as <model>.civitai.info after checking it names the
same file (size, shape and hash). The old Python HTTP call stays gone.
release_preflight now allows a raw fetch() only in js/cache_guard and
js/lora_loader. SECURITY.md, AGENTS.md, the README and the LoRA Loader help
page describe the change.
The wheel, Ctrl+Shift drag-zoom and middle-button pan never reached the
graph from over Compare, the crop previews, the video viewers or any control
on a card. One pack-wide listener hands them back, the way ComfyUI does for
its own text boxes. Panels that scroll keep the wheel until they reach an end.
Wired straight into a Qwen Image 2.1 sampler, the node's latent made a
picture twice as wide and tall as the size shown (1024 x 768 came out
2048 x 1536). The latent now carries how far it is downsampled, as
ComfyUI's own empty-latent nodes do, so ComfyUI fits it to a model that
packs pixels differently. Other models are unchanged.
The MaskEditor saves its picture into the image widget's stored value
without calling the widget back, so the node kept showing the old picture
and nobody could tell a mask was there. The stage now follows a source
changed that way (on the node's draw, and when the MaskEditor sets the
node's preview, which covers Nodes 2.0 too), keeping the framing.
The mask is the picture's alpha. The stage reads it once per picture by the
run's own 90% rule and draws the picture as the run uses it: kept pixels
solid, painted parts empty over the fill, with Show blend's teal on top.
The teal turns, crops and pads with the picture, in the full editor too,
and Show blend's paste mask now includes the painted parts. A gear on the
image source box turns the teal off for this browser.
Four orange corner handles sit outside the canvas corners, on the same
ring as the side diamonds. Dragging one makes the canvas bigger or
smaller and keeps its shape: the padding on that corner's two sides
changes and the opposite corner holds still. Alt (Option on a Mac)
changes all four sides about the centre, and can be pressed or let go
mid-drag. A corner stops when one of its sides runs out of padding.
Two Centre buttons, on the node face next to Fill and Feather and in the
editor's Padding section, put the picture in the middle side to side or
top to bottom. They only move padding from one side to the other, so the
canvas keeps its size and a lit ratio stays lit; with Divisible by on,
its strip counts, so the visible bands come out even.
The rotate knob took the spot the top-right corner handle now needs, so
it moves to the nearest free gap on the handle ring when blocked, and it
keeps its place on its arm while it turns. In the editor, Alt-drag still
pans everywhere except on a corner handle.
Frontend only: the node inputs are unchanged.
A picture turned to 180 degrees could not be turned back with the knob:
dragging one way it stayed at -180, and the other way it snapped back to
-180 once the pointer passed straight left of the picture. Turning
clockwise through 180 also made it jump.
The turn was the pointer's angle minus its angle when the drag began.
atan2 flips from +180 to -180 straight left of the centre, so crossing
that line read as a whole turn back, and the clamp to -180..180 then
pinned the picture at the end of the range.
The knob is now followed move by move, each step the short way round
(knobStep), and the rotation wraps instead of clamping (wrapDegrees):
past 180 it carries on from -180. The Degrees box keeps its range.
Round canvas to on Image Crop + Rotate + Pad and both Video Crop + Rotate
+ Pad nodes is now labelled Divisible by. Its tooltip says what it does
(a few pixels of fill on the right and bottom so the width and height
divide evenly by the number), when to use it (models that need sizes
divisible by 8, 16 or 32) and that 1 is off. The input is still
canvas_multiple, so saved workflows keep their value.
Its arrows and drags added 8 to whatever was there, so from 1 (off) they
went 9, 17, 25. makeScrubInput gets a snap option: whole steps land on
multiples of the step (1, 8, 16, 24 and back down to 1), while Shift's
fine step and typed values are left alone. The resize Step box on the
same nodes and Multiple on Load Image + Pad had the same problem and use
it too.
The editor's status panel now refreshes when Divisible by changes, as it
already did for every other size control.
ComfyUI sends no cache header for .mjs, and the 2.5.1 no-cache header cannot
reach a copy the browser already stored. Firefox then kept running files from
before the update, even through Ctrl+Shift+R, and the preview bar, cards and
editors failed to load.
The server now adds ?v=<tag> to the relative .mjs imports of the pack's
scripts as it serves them. The tag changes whenever any script changes, so
after an update the browser asks for addresses it has never stored. Files on
disk are unchanged. A test fails if a script names a .mjs in a way the tag
would miss.
ComfyUI sends no cache header for .mjs, so after a Manager update a browser
could reuse old shared modules while loading the new entry scripts. The new
entries then imported functions the old modules lack and about a third of the
pack stopped loading.
The pack now serves its .mjs files with Cache-Control: no-cache, and a small
entry (js/cache_guard) refreshes the stored copies and asks for a reload when
a tab runs older files than the installed version. It replaces the old
stale-cache toast in the appearance entry.
The unpainted check counted a flat block only within 10/255 of the fill.
Krea 2 can hand a 128 gray fill back as a flat 114 to 118, so a picture
with gray corners got no word. Flat blocks now count within 20/255, and so
does the picture's own flat area that is taken off first, so a flat wall
continued flat still stays quiet.
The message told still pictures that dark footage paints better when
brightened first. That line is now only for videos, in plainer words.
The warning had no changelog entry yet; it has one now.
Tone match ran about 3,700 small torch operations per picture. PyTorch
spreads each one over every CPU it sees, and a cloud container can show it
far more CPUs than it may use, so each one waited its turn: on a RunPod
container it added 37 to 146 seconds to a 1.6 MP picture.
The blurs now run once over every layer they need, and the fit, the
held-out check and the curve inversion run over whole arrays: the band's
samples are summed per knot segment (sorted once, one reduction per pass
for all three fits), every curve is solved in one batched call, and the
curves are read and inverted over the whole picture at once. That leaves
about 18 multi-threaded operations. A single picture reuses the values it
found while measuring when the correction is applied.
The result matches the old code: same decisions, and at most 0.0002 of one
8-bit level apart on real runs (the fit's sums are only added in another
order). The old code is kept in the tests as a reference, and a new test
pins the two together on turned, straight, dark, thin-feather, clip and
alpha cases.
Krea 2 Inpaint Masked keeps a person you leave unpainted (Mask Refine's
Max hole size at 2). Klein 9B Outpaint blends its seam in, with Tone match.
Qwen Image 2.1 Edit + Realign fills see-through parts with gray and its
prompt writer keeps what the request doesn't name. The Krea 2 Outpaint and
Rotate + Outpaint notes add a tip for pulling far on one side, and the
examples that need this release say so. LTX 2.3 Video Outpaint makes room
for the taller clip node. Every file was re-saved by the frontend and
checked in the classic view and in Nodes 2.0.
The changelog no longer says Krea 2 Outpaint keeps its 3:4 ratio lit.
The same pixels as before, from integer maths over the see-through part's
bounding box only: a 17-megapixel cutout no longer spends seconds on
floating-point work across the whole canvas.
Krea 2 Outpaint and Krea 2 Rotate + Outpaint describe the canvas before
painting it. They now read the pad node's prompt_image, where see-through
parts of a picture show as white, so a cutout is described on a real
backdrop instead of the gray fill. For a picture with no see-through
parts prompt_image is the canvas itself, so nothing else changes.
A see-through part of a picture - a cutout's missing background, an oval
photo's corners, a hole - kept its stored colour blended over the fill.
That colour is often black or the old background, so the model got a
dark or light ring round the picture and Stitch Inpaint pasted part of it
back. A pixel less than 90% as solid as the picture's most solid pixel is
now empty, like a corner a turn leaves: filled with the fill colour,
marked in the mask, new area for both seams. Kept pixels are fully solid
in their own colour.
A new last output, prompt_image, is the image with see-through parts on
white instead of the fill, for the node that writes the prompt: shown the
gray fill, a prompt writer describes a gray backdrop and the model keeps
it. original shows see-through parts on white instead of black.
A picture at least 90% solid everywhere goes through exactly as before;
the new tests hold every output and both seams to the old transform.
Paint everything except a person or a pet, turn on fill holes, and it
filled the unpainted subject too, so the part meant to stay was repainted.
The new optional max_hole_size input (a percent of the picture, 0 to 100)
sets the biggest hole fill holes fills. The default, 0, fills every hole
exactly as before, so saved workflows and API prompts run the same; at 2
the gaps a quick brush leaves still fill and a subject left unpainted
stays. Areas touching the picture's edge are never holes, as before.
- Hole sizes come from scipy's labels, the same 4-connected holes its
fill finds; the torch fallback labels the same 8-connected holes as its
own fill. Each picture in a batch is measured on its own, after expand.
- The card gets a Max hole size row with a % unit, dimmed while Fill holes
is off, and a resetUnknown fallback of 0 for 2.x saves that hold the
card's empty value in its slot.
- Tests: a hole above the limit stays, holes up to it fill, 0 matches the
old fill, edge-touching areas never fill, the torch fallback agrees,
batches, and older saves opening with 0.
- Node API snapshot, help page and changelog.
holdsUnknownValue judged only switches and choices, so a number input
appended after a release could not be given a resetUnknown fallback: an
older save hands it the card's empty value, and it stayed there, which the
prompt then fails to convert. A number widget now holds only a finite
number. Only widgets named in a card's fallbacks are ever reset, so no
existing node changes.
A node sized for the old face could grow into the node below once the new
notes showed. Now:
- The size line names the lit ratio where it acted ("pad to 16:9
1821x1024") instead of adding a line, and "already 9:16: pick another
ratio or turn it" sits on the same line when it fits.
- The Even out, To start and Full clip buttons fit their lines.
- "= the whole clip. Shorten it to move IN" fits beside the Length on the
example workflow's node.
- A new picture only ever shrinks the picture area to its shape; it never
grows the node.
Many shared workflows save their loaders blank so people load their own
picture. Pressing Run first started the run, and core Load Image failed
inside it with "[Errno 21] Is a directory". Now the run stops before it
is sent: one message, "Load a picture first:" and the node's name, and
the loader gets a red outline (on the classic canvas and in Nodes 2.0).
It covers core Load Image, Load Image (as Mask), Load Image (from
Outputs) and Load Video, and the pack's Load Image + Pad, Image Crop +
Rotate + Pad, Load Video and both Video Crop + Rotate + Pad nodes (the
file or the local path, whichever the mode reads). It looks at the
prompt the frontend is about to send and walks it the way ComfyUI does,
so bypassed and muted loaders, loaders fed by a link, loaders no output
needs, loaders behind a lazy input and outputs left out of "Queue
selected output nodes" never stop a run. Prompts sent straight to the
API are not checked.
The frontend has no extension hook that can stop a queue, so
app.queuePrompt is wrapped; it only reads, and builds the prompt a
second time only when a blank loader is on the canvas. Anything
unexpected lets the run through unchanged.
When ComfyUI does reject a prompt, a check that reads several inputs
comes back once per input. api.queuePrompt's answer is tidied so each
AusBoss node shows one entry per message, on the input the message is
about. Other packs' nodes are left as ComfyUI sends them.
The test carried a copy of the frontend's space-sharing code, and ran
our helper only inside that copy. It now checks what the helper
promises in our own words: no computeSize left behind, a floor with no
ceiling, floors read on each call and made safe, options kept, and a
hide and show that leaves the panel growing again, using the pack's own
hideWidget and showWidget.
The snapshot test needed a ComfyUI checkout, so CI always skipped it and
a renamed or reordered input passed every CI check. The same comparison
now also runs on an offline load of the pack: a standard-library Python
with torch, NumPy, Pillow, PyAV and SciPy replaced by stand-ins that
accept anything. Input and output names come from the node code itself;
with ComfyUI, a test checks that the offline load describes every node
exactly as the real load does. The ComfyUI load now runs in CPU mode, so
it never touches a GPU.
Also:
- The "became required" rule had no test of its own: its example also
broke the input order, so deleting the rule failed nothing. It now has
two cases only that rule can catch.
- --update refuses to write a snapshot that would break saved workflows
or API prompts, unless --allow-breaking says the node never shipped.
Widget values reach the loaders through ComfyUI's /prompt route, and
resolve_input_path and local_preview_allowed are what keep those reads
inside ComfyUI's input, output and temp folders. No test covered them:
removing the outside-the-input-folder check, the network-path check or
local path mode's network check passed every test.
tests/test_media_paths.py covers climbing out with "..", absolute paths,
links pointing outside, "[output]" names, network paths refused before
the path is touched (even when a ComfyUI folder itself sits on the
share), and local path mode. The guards need neither torch nor PyAV, so
the media module's heavy imports are stubbed when missing and the file
runs in CI. Each guard's removal fails at least two tests.
The nvenc encoders are in the libav build with or without an NVIDIA card
and only fail when they open. That happened on the first frame, outside
the handler that explains it, so picking "mp4 h264 nvenc" without the
card ended in a bare "Unknown error occurred: avcodec_open2(...)". The
encoder now opens as the stream is set up, and the message leads with
what to do: the nvenc formats need an NVIDIA GPU, pick "mp4 h264". Other
encoders that fail to start name themselves without the NVIDIA advice.
Core Load Image hands out a 64x64 mask of zeros when the picture has no
mask painted on it, and picking a new picture after painting one gives
the same. Krea 2 Inpaint Masked then stopped inside Crop For Inpaint with
"Mask size (64, 64) does not match image size (1216, 832)".
Crop For Inpaint, Color Match and Mask Refine (with its picture wired)
now say "No mask painted: right-click your picture, choose Open in
MaskEditor, paint the area, then click Save." A mask made for another
picture names both sizes. LaMa Inpaint used to resize the blank stand-in
and hand the picture back unchanged; it says the same now. An empty mask
of the picture's own size still runs as before: Crop For Inpaint takes
the whole picture and stitches it back untouched.
The help pages, the README and the changelog use the new names (Round
canvas to, Frames for, Server file, the Resize switch) and describe the
always-scrubbing timeline and its grip, the padlock rules, the "already
this shape" note, the resize warning and Even out, the preview switch
bringing its picture back, Compare's labels and the Workflow Note's Use it.
A VALIDATE_INPUTS that takes **kwargs makes ComfyUI skip its own range
and list checks for every input, and a failed check comes back once per
input: Image Crop + Rotate + Pad without a picture showed 20 copies of
"Select or upload a source file first". Each validator now names only
what it reads.
- Image Crop + Rotate + Pad, Load Video and LaMa Inpaint read one input
and report one error, on it. Video Crop + Rotate + Pad (Frame and
Clip) read the three that choose the source. LoRA Loader reads the
stack and on_missing; Save Image reads its two names and output_dir.
- Save Video has no validator any more: ComfyUI's own check holds a
typed fps to 0.01-240.
- Load Video and the Clip node check the trim window, force_rate and
Fixed frames as they run, where a wired bound has its value. They are
async, so ComfyUI starts them ahead of other nodes.
- The sources still skip ComfyUI's list check on purpose, so uploads,
subfolders, MaskEditor saves and a stale video choice in local path
mode keep working. The path checks behind them are unchanged.
- crop_aspect_ratio accepts any whole-number W:H, as the transform
always has, so a workflow made with someone else's presets still runs;
anything else is now refused before the run.
tests/test_validate_inputs.py pins what each validator names (runs in
CI) and, with ComfyUI, drives ComfyUI's own validation.
- A node dragged taller while its preview was off keeps that height when
the preview comes back.
- A new picture with no ratio lit starts with the padlock off, as the
untouched picture has no shape to hold.
The note said "installed" for a model kept in a subfolder while the
workflow's loader still asked for the bare file name, which ComfyUI could
not find and marked red. Such a row now says where the file is ("in
LTXV 2.3/") and has a Use it button that points those loaders at the found
copy. The check stays on the ComfyUI server's own model lists.
The A | B badge stayed in the middle of the picture while the split line
moved. Now A rides just left of the line and B just right of it, on the
picture each one names; with one picture showing, its label sits alone.
Shift is the fine step on every number box, but on whole-number boxes such
as Feather the fine step was the step, so Shift did nothing. There it now
slows the drag four times instead. Pressing or letting go of Shift during
a drag carries on from the current value instead of jumping.
The padlock:
- Reset and tapping the lit ratio turn it off.
- It does nothing on the untouched picture (the row says Source), so a
trim there only trims.
- The row says Held while it is on, and a locked drag splits the new
bands evenly (each move is solved from where the drag started).
- Reset crop also removes the bands the lock added.
- The lit ratio and the lock share the pack's solid teal "on" look;
hover is a light outline.
Shapes and sizes:
- A picture within about 1% of a ratio counts as that ratio: tapping 9:16
on a nearly 9:16 photo adds no 1 px band.
- While a ratio is lit, the line under the picture says what it did,
and "Already 9:16: nothing to add. Pick another ratio or turn it." when
there was nothing to do (a saved ratio re-applies to every new picture).
- The resize warning reads "1.5% taller: each side rounds to 32 px"; its
tooltip names a Step that avoids it, and in pad mode Even out adds a few
pixels of padding so nothing stretches.
- Fit dims and says "pick a ratio first" until a ratio is lit.
- Over the picture, the cursor and small arrows show only the ways it can
move; with nowhere to go it is a plain pointer.
Drawing:
- Added space is drawn in the real fill colour with a faint hatch, and
only the picture the crop cuts away is darkened, so black bands on the
black stage show.
- The rotate knob keeps clear of the padding diamonds and crop squares.
- On a big node the controls stay at most 460 px wide, centred, and only
the picture grows. A new picture fits the picture area to its shape,
never taller than the height you gave it.
Words: Align is "Round canvas to", Local path is "Server file", Resize is
an off | on switch, and the canvas row labels read like the others.
The full editor is built from the node's own controls: the ratio row with
its turn button and padlock, the Fit switch, and number boxes for the
rotation (Shift for 0.1 degree) in place of the coarse slider. Ratios
from ausboss_presets.json that no button shows sit in a More list. The
size box keeps clear of the handles, and the editor's button style no
longer reaches the number boxes.
Switching a preview off and on again (Mask Refine, LaMa Inpaint, Select
Frame, Save Image) left an empty box until the next run, and the node came
back shorter than it was.
Now the node remembers its height when the preview goes off (saved with
the workflow) and returns to it, with the last result in the box. If the
node ran while its preview was off, the box says "Run the workflow again
to see it here" rather than showing an older picture. With nothing to show
yet it says "Run the workflow to see it here".
Mask Refine's AUTO button now says what it does in its tooltip.
With Length on, pressing between IN and OUT was a hidden "move the whole
clip" grip. With a Length as long as the clip it had nowhere to go, so the
bar and IN seemed frozen and nothing said why.
Now a press or drag anywhere on the bar moves the playhead, Length on or
off. The kept part carries a small grip that slides the clip (IN still
slides it while Length is on); a free window slides with its IN and OUT
together. When the Length already covers the whole clip, the Length row
says so: "= the whole clip. Shorten it to move IN."
Also:
- Snap reads "Frames for: LTX (8n+1) / Wan (4n+1) / any"; the stored
values are unchanged.
- "To start" and "Full clip" look like buttons.
- Turning Length off no longer moves the playhead, as the help page says.
Both Krea 2 Outpaint and Krea 2 Rotate + Outpaint now pass the pad mask to
Krea 2 Encode, so AnyPaint sees the new area in the picture's own colour
and paints it instead of copying the gray padding. They also feather the
join 32 px instead of 12, the width of the band AnyPaint was made to
redraw: a thin strip of new area had come back as a flat band with a hard
line at 100%, and with 32 it continues the picture.
At strength 1 the outpaint IC-LoRA left the new area black on some clips,
most often when the prompt described a close, framed shot. The LoRA now
runs at 1.5, and the prompt instructions ask for a wide shot of the whole
place with what fills every side, so they work for side and top/bottom
padding alike.
An outpaint whose new area still shows the plain fill color used to come
back as a finished-looking picture with bars. Stitch Inpaint now measures
how much of the new area is still flat at the fill color, beyond what the
source picture itself has, and when a quarter of it (or half of any
checked frame) is left, says so in the console and in a toast.
The check lives in its own helper, works for images and video and for any
fill color, and stays quiet on dark scenes that are painted.
Blend in kept the model's colour as painted, so a model that paints
lighter, darker or warmer left the new area off-tone. Classic's Tone
match is no answer on turned pictures: it reads the grey rim a turn
leaves on the picture's edge as drift, and its flat offset lifts the
black of a dark new area.
With Tone match on, blend in now compares the model's version of the
strip it redrew with the picture at the same pixels (tone layers over
the same clean picture pixels, clipped highlights left out), fits the
drift as curves over brightness with a pinned and a free part, checks
the fit on held-out tiles, adds a local gain along the edge only when
that holds out too, and takes the result back off the model's picture
before the blend. Blacks stay black; nothing changes when the check
fails, the model kept the colours, or there is no feather to read. A
clip is measured once.
Classic is unchanged (its pins), and blend in at Tone match 0 is pinned
to the 2.4.0 stitch. The card no longer dims Tone match under blend in.
A dark or colourful picture padded with the #808080 gray came back from
Krea 2 AnyPaint with the gray still there: a gray frame around the picture,
or gray corners on a turned one. AnyPaint was trained with the new area of
its reference filled with the median colour of the picture, so a gray patch
next to a dark photo reads to it as part of the scene and it paints it back.
It did the same with every Krea 2 model file tried.
Krea 2 Encode gets an optional mask input, appended last. When it is wired,
the new area of the reference (mask above 0.5) is refilled with the median
colour of the rest before it reaches the VAE and the vision tower. The
canvas the rest of the graph uses keeps its gray. Without the link nothing
changes.
Both panels filled only from onExecuted. Going to another workflow tab and
back rebuilds the nodes and restores the tab's results in ComfyUI's store
without an onExecuted, so they said "Run to preview" over a result they
had just made, in both renderers. On onNodeOutputsUpdated (a tab's results
restored) an empty panel loads its node's stored record. The output key
helper moves to shared/prompt_scope.mjs for the three users.
Prototype of the ratio row, for review. A chip used to stay lit after a
drag changed the shape (16:9 lit on a 1889x1280 canvas), a second tap on
it locked and a third cleared every pad, the portrait/landscape button
threw away hand edits, a locked diamond dragged inward did not move, and
Crop | Pad looked like two more ratio chips.
- A chip is lit only while the canvas (crop plus padding, before Align)
has its ratio; the pick (properties.ausboss_fit_aspect) is dropped when
a gesture leaves it, so a new clip is only fitted to a shape the canvas
still has. Without a picture a tapped ratio waits for the first one.
- Tap = reach the ratio (pad, or crop with Fit on crop), tap the lit one =
back to the whole picture. No other chip state.
- Holding the shape is a padlock button (ausboss_aspect_lock): drags keep
the lit ratio, or the custom one, from the moment you grab a handle.
- The orientation button turns the shape: pad mode pads the current crop
to the turned ratio, crop mode turns the crop box about its centre.
Nothing to turn (no picture, untouched, square): only the labels turn.
- Custom / Source caption, and the real ratio on the size line.
- Fit is a segmented pill; switching it re-reaches a lit ratio.
- Rotation keeps a lit ratio (and a held custom one).
- Dragging the picture where the crop spans the source slides it inside
its padding (slidePadding).
- The editor's Ratio list applies at once; Hold shape is the padlock.
Saved properties and widget values keep their names and meaning; an old
locked chip opens with the padlock on. Pure rules in transform_geometry
(canvasSize, aspectMatches, turnAspect, turnedCrop, padAround,
slidePadding, ratioLabel, isUntouched) with node:test coverage.
The stage could be squeezed to about 130 px on all three nodes: the
declared floors counted the rows around it short, and the Clip node's rows
alone take 450 px. A padded portrait clip ended up a sliver in a 16:9
canvas.
- The stage's floor follows its width (stageHeightForWidth: 0.6 x width,
200 to 340 px). The panel's minimum is the measured rows around the
stage plus that floor, so a corner drag stops there; before the panel
is on screen a per-kind estimate stands in. A new node opens at it.
- keepStageRoom grows the node to the same floor when a row appears later
(Resize, a wrapping readout), and sets it as the stage's CSS minimum so
Nodes 2.0, which sizes nodes by content, holds it too.
- exactMinWidth and holdVueNodeMinWidth make 330 the real minimum width in
both renderers (the classic corner drag jumped to 434; Nodes 2.0 went
down to 225 and crushed the chip row).
- The source card's "Choose an uploaded ..." line hides once a file is
chosen, which is the room older layouts need for the new floor.
- The LTX 2.3 example moves the two nodes under the Clip node down for its
new height; the contract and a live load find no overlaps.
Every workflow tab in a page shares one server connection, and ComfyUI
files a node's results by node id. A run from one tab that finished while
another tab was open landed on the open tab's node with the same id. On
Video Crop + Rotate + Pad -> Clip that was a second video under the
buttons that grew the node over the one below; Show Text and Seed also
saved the other workflow's text and seed into this one, and Save Video and
Compare replaced their own result.
- The three Crop + Rotate + Pad nodes stand down ComfyUI's image and video
previews (they send no picture of their own), in both renderers.
- suppressCoreVideoPreview sets hideOutputImages, so Nodes 2.0 no longer
draws Save Video's clip a second time under its player.
- shared/prompt_scope.mjs notes which graph queued each run (the queue
call is wrapped for the prompt id). For a result from another tab, the
AusBoss node here gets this tab's own stored result back, every pack
onExecuted handler returns early, and the result is handed to the owner
tab's AusBoss nodes when that tab is open again. Core nodes are left
alone.
Tests: prompt_scope (scope rules plus a policy check that every
onExecuted handler asks isForeignRun first) and core_preview (every
transform kind suppresses both previews; video suppression sets
hideOutputImages) fail on main and pass here.
The editor's model table had columns for file, folder, size and URL but
none for the row's note, and the form read each row back without it. Save
(from the form or the JSON view) wrote every model with an empty note, so
the grey line under each file name was gone after the first edit. On a
note with model notes, Cancel and Escape also asked to discard changes
when nothing had changed, because the form never matched the saved card.
The table now builds its header and cells from MODEL_COLUMNS, which has
one column for every field a model row keeps, and reads rows back with
rowFromCells. That adds a Note column. tests/workflow_note.test.mjs checks
that the columns cover every kept field and that an unchanged table saves
back the same card.
On frontend 1.53.6 (classic canvas and Nodes 2.0), the LTX 2.3 Video
Outpaint example keeps its five model notes through a how-to edit and
Save; Cancel with no edits closes without asking; a note edited in the
new column and a model added with a note both show and survive a save and
reload. The table fits the dialog without scrolling sideways from 700 to
1800 px wide.
Groups that shared a bottom edge before the Nodes 2.0 sizing share one again: the shorter ones grow to the tallest. In the Krea 2 Outpaint and Krea 2 Rotate + Outpaint boxes, the two prompt groups that were 2 px apart now share a bottom too. Only group heights change; values, links, titles and the API prompt are unchanged.
Nodes 2.0 draws some nodes taller than classic, so every group now holds its nodes with 20 px in whichever renderer draws them bigger, and what sits below a grown group moves down with it. Show Text moves below Generate Text in Qwen Image 2.1 Edit + Realign, and the Watermark Remover's Reroutes get room above the nodes they feed, so nothing overlaps in either renderer. Each Workflow Note gets 12 px of spare height in classic. Values, links, titles and the API prompt are unchanged.
A note saved a few pixels shorter than its content showed a scrollbar and
hid its last row. The LTX 2.3 Video Outpaint note in the smaller example
layout (440 x 810) was 6 px short, so its links sat behind the scrollbar.
The classic canvas keeps a node's saved height; Nodes 2.0 already sized
the note to its content.
The note now measures its card's natural height at the node's width and
grows the node to fit when a workflow opens, after an edit, after the file
check, and when the node's width changes, since the text rewraps. It never
shrinks the node. The load fit runs before ComfyUI snapshots the opened
workflow, so a note that grows on load does not mark the workflow modified.
A corner drag stops at the fit, as it does in Nodes 2.0: holdNodeMinHeight
lifts the node's computeSize height to the measured floor. A card less
than half a pixel short is left alone, because Chrome rounds that away and
draws no scrollbar. Nodes 2.0 is untouched.
Undo and redo rebuild every node in place, with the same ids. Nodes 2.0
usually keeps each node's face, and the slot that holds a DOM panel only
mounts an element when it is first made. So after Ctrl+Z every AusBoss
node kept showing the removed node's panel, the rebuilt node's panel
never reached the screen, and changes made on that face never reached
the queued prompt.
When an AusBoss node is removed, the panels it has on screen are
remembered by graph, node id and widget name. When a node with that id
is configured, each of its off-screen panels takes the place of the
remembered one if that one still sits on this node's face. Classic mode,
new and pasted nodes, and faces the frontend mounted itself are left
alone.
A transform node is created on the first file in the input folder. When
a workflow saved with an empty loader configured it blank, the node kept
remembering that first file, because a blank source never replaced the
remembered one. Picking the first file then looked like no change: the
workflow's saved rotation, crop and padding stayed and a lit ratio was
not refit. Any other file reset as it should.
The node's first sync and a workflow load now remember what the node
holds, blank included (rememberedSource in transform_geometry.mjs). Only
a blank the user makes, such as Local path before a path is typed, keeps
the last source, so going back to the same file is still no change.
Covers the image node and both video nodes.
In Nodes 2.0 a node's saved height is only a minimum and its content can
make it taller. The preview panel's picture was in normal flow, so a
portrait result drawn at full width grew Save Image from 730 to 1276 px in
the Krea 2 Outpaint example, over the Run Timer under it. Nodes 2.0 also
drew its own copy of the result under the panel, because the panel never
set hideOutputImages.
- The stage's picture is out of flow and centred, so the stage gets its
size from the node and the picture scales to fit, in both renderers.
- suppressCoreImagePreview sets hideOutputImages; classic never reads it.
- In Nodes 2.0 that copy was the only thing filling node.imgs, so the
panel falls back to the node's stored output (app.nodeOutputs) and
re-reads it when a workflow tab restores its outputs. node.imgs still
wins where it exists, so classic keeps its old path.
Select Frame, Mask Refine and LaMa Inpaint share the panel and grew the
same way. Core Save Image and Preview Image keep their size in Nodes 2.0.
Classic is unchanged.
Tests: the stage-picture guard in panel_guards, hideOutputImages in
core_preview and the stored-output lookup in input_preview fail on main
and pass here.
The example finds the mark with ComfyUI's built-in SAM 3.1 instead of
ComfyUI-RMBG, so it needs no other node pack and no extra download on the
first run. One set of settings feeds both the one-frame check and the whole
clip, and the layout matches the other examples. The README table and the
CHANGELOG describe the refreshed examples.
The same outpaint on 13 top-level nodes instead of 23, with the prompt
writing and the painting in two boxes. Both LoRAs load in one LoRA Loader,
the prompt instructions sit in their own Text node, and the video node saves
no padding with its 16:9 ratio chip lit, so a new clip is padded to fit.
Same models and output.
The example now pads with Image Crop + Rotate + Pad's ratio buttons, so the
padding is worked out for whatever picture is loaded instead of the
sample's pixels. The auto description describes only the setting: when it
described the person in a tight portrait, Klein painted a second face under
the join. Smaller layout with two boxes; needs AusBoss nodes 2.4.0. The
thumbnail shows the new result.
The same edit on 14 top-level nodes instead of 24: the prompt writer and the
edit sit in two boxes. Node titles use the real node names, the model
loaders are color-coded and the note is down to three steps. The graph and
its output are unchanged.
Both examples open with an empty image loader but still saved the sample's
turn and padding. Picking the first file in the input folder kept them, so
that picture got the sample's 12 degree turn or its 1045 px bands. They now
save no turn, no crop and no padding; Krea 2 Outpaint keeps its 3:4 ratio
chip lit, so a new picture is padded to 3:4.
The example saved Stay in picture off, so a big painted area was cropped past
the picture edge and painted at a smaller scale. It now saves it on, saves the
Stitch Inpaint seam as classic, and says it needs AusBoss nodes 2.4.0. The
thumbnail shows the new result.
Workflows saved by the 1.x releases also end in a panel's empty value: the
preview panel on LaMa Inpaint and Mask Refine (Select Frame from 1.2.0) and
LoRA Loader's rows. The inputs added in 2.0.0 sit in those slots, so preview
opened as "" (the switch showed on, but the prompt ran without a preview)
and on_missing opened as "" (the menu showed Stop on missing LoRA off while
the node still stopped the run). Each now opens on its default, which is
also what an API prompt without the input runs with: the three cards reset
preview through resetUnknown, and LoRA Loader, which has no card, applies a
RESET_UNKNOWN when a workflow opens. On a real 1.x save (pack 1.3.0 saved by
frontend 1.47.12) only those four values change.
The guard's history now reaches back to 1.0.0. Mask Refine's preview was
inserted ahead of an existing socket rather than appended, so the table now
lists each node's last input as of this guard and APPENDED_SINCE declares
everything added since the node first saved an empty value. The fallback
check also reads RESET_UNKNOWN, and tests/test_card_fallbacks.py becomes
tests/test_saved_fallbacks.py, which checks both kinds.
Every card and DOM panel was added with options.serialize = false, which
only keeps a widget out of the queued prompt. The frontend saves each
widget whose own serialize is not false and hands saved values back by
position, so each card and panel saved an empty value of its own after the
node's real values, and an input appended to the node later opened holding
it (Stitch Inpaint's seam, Crop For Inpaint's keep_inside). Every DOM widget
now also sets widget.serialize = false, as the frontend's own previews do,
so new saves carry only real values.
Older saves still end in those empty values, and loading them now skips
the cards and panels. On every node that saves by position they come after
all real values (Save Image's card sits mid-list, but Save Image saves by
name), so nothing moves. On frontend 1.53.6 the examples (the 21 from
before the trim and the 7 now) and 14 older saved workflows queue
byte-identical prompts before and after, keep every value, and save and
reopen cleanly.
An input appended later still needs a fallback for those older saves.
tests/saved_widget_values.test.mjs checks that every DOM widget sets the
flag, that the examples keep their values with the panels skipped, and that
each input appended to a node whose older saves end in an empty value is
declared and, as a widget input, has a card resetUnknown fallback that
fires. tests/test_card_fallbacks.py (ComfyUI's Python) checks each fallback
is the input's own default. AGENTS.md states the rule.
Every image and video loader in the seven examples is saved with no file
picked, in both saved widget copies. Remove the three sample pictures the
new examples added, put the Registry archive budget back to 12 MB, and
update the README examples section and the CHANGELOG.
A workflow saved before 2.4.0 holds the widget card's own empty value in
the slot keep_inside took when it was appended. The frontend restores
widget values by position, so keep_inside loaded as "", the card showed it
off and the queued prompt sent "", which ComfyUI reads as False: the old
padded crop. An API prompt saved before 2.4.0 has no keep_inside and runs
with the node's default, True. The same workflow gave two different crops.
The card now puts True, the node's default, back on a keep_inside that is
neither true nor false before it draws, the way it already resets an
unknown Seam on Stitch Inpaint. The resetUnknown check moves into
widget_card_math.mjs (holdsUnknownValue, resetUnknownValues) and learns
switches: the old check only knew a choice's options, so it would have
reset every saved switch. A saved true or false is left alone.
The backend is unchanged. ComfyUI converts a BOOLEAN "" to False before
VALIDATE_INPUTS or the node sees it, so an API prompt that carries "" (an
API export made on 2.4.0) still runs with it off.
Replace it with the version that pads with Image Crop + Rotate + Pad,
with its sample picture and a new thumbnail. Point the README's Load
Image + Pad section at Klein 9B Outpaint, and update the example table,
the outpaint caption, the Krea 2 Outpaint Model Patch help page and the
CHANGELOG. Raise the Registry archive budget to 18 MB.
A file name in Load Image, Load Image + Pad, Load Video or Image Crop +
Rotate + Pad is one the stranger doesn't have. ComfyUI flags the node red
when the workflow loads, with a toast and a Setup required entry, until
they pick a picture. Saving the loaders blank loads clean.
Adds the rule to the example workflow section, and says "model loader" in
the bullet about official file names so the two don't overlap. The
existing examples are not changed here.
Keep Krea 2 Outpaint, Klein 9B Outpaint, LTX 2.3 Video Outpaint,
Qwen Image 2.1 Edit + Realign and Simple Video Watermark Remover, and add
Krea 2 Inpaint Masked and Krea 2 Rotate + Outpaint with their sample
pictures. Remove the other sixteen examples and the sample picture only
they used.
Update the README example table and links, the Realign to Source help
page, a test comment, the CHANGELOG and the pyproject description, and
raise the Registry archive budget to 13 MB.
The card wrote a widget's value and ran its callback, but never called
node.onWidgetChanged, which is where ComfyUI's frontend re-scans a node
for missing media. Picking an existing file in Load Image + Pad's Source
list left the red outline, the "Media input missing" toast and the Setup
required entry on, still naming the old file.
Every card control now writes through commitWidgetValue, which sets the
value, runs the callback and then calls onWidgetChanged, in the order
LiteGraph's own widgets use.
Adds tests for it, plus a changelog entry.
The card keeps its old look and height. Seam has no row and no socket
now: a small gear in the card's top-right corner opens a menu with the
two choices, each with one plain line. With blend in on, Tone match and
Fix edge halo stay in place but dim and stop taking input, with a hover
hint, and a "blend in" chip beside the gear shows the mode.
The seam widget is a socketless combo, so the frontend makes no input
slot for it in either renderer; a stale unlinked slot from an earlier
save is dropped on load. The backend input is unchanged.
The widget card gains two generic options for this: rows can be muted
by the current values, and a card can carry small corner tools that
every row's control keeps clear of. A gear-menu choice can list one
line per option.
Blend in suits turned pictures and outpaints with Tone match off. With straight padding and Tone match on, classic usually looks as good, because the model's slight tone drift stays as painted in blend in. The help page also limits the unchanged round-trip promise to the classic seam.
Seam is a new optional choice, appended after color_match. "classic"
is the default and the old stitch, bit for bit. "blend in" hands the
model's picture over to the source by depth into the picture: colour
and tone change over slowly, fine detail changes over in a short ramp
where the two line up, the new area keeps the model's pixels, and the
deep picture keeps its own. There is no global tone shift, so
color_match and fix_edge_halo do not apply and the card hides them.
The depth map comes from the generated-area mask of a Crop + Rotate
+ Pad stitcher, or from the source rectangle of a Load Image + Pad
stitcher, with sides flush with the frame left out. It is computed
once per stitcher and shared by every frame, with scipy when present
and an exact torch fallback otherwise. Crop For Inpaint stitchers
keep the classic stitch. With blend in, blend_mask is the hand-over
weight.
An older workflow can carry the card's empty value in the slot Seam
now takes; the card resets it to classic on load and the node accepts
it as classic. Classic output is pinned against the previous code for
Load Image + Pad, Crop + Rotate + Pad, Crop For Inpaint and clip
stitchers.
A wired input reaches VALIDATE_INPUTS as None, and the window check called
float() on it, so wiring anything into start_seconds or end_seconds made the
whole prompt fail validation with a TypeError. Skip the window check while
either bound is wired; the file check and the check for typed values stay,
and the loader checks the window again when it runs.
Adds a test for a wired start, a wired end, both wired, and for the checks
that still apply, plus a changelog entry.
Resolution Master is renamed Latent Size: the old name belongs to another
node pack. The node id AUSBOSS_NODES_Resolution is unchanged, so saved
workflows and API graphs load and run as before. Workflows only store a
title that differs from the default, so they pick up the new name; a saved
title that starts with "Resolution Master 🆎" gets the new name in its
place on load. The example, its thumbnail and the README image follow.
- Latent Size can be resized: wider stretches the chips, fields and size
picks, and taller gives the stage the room. It opens at 340 x 531 as
before and goes down to 320, where the size row tightens so W, H and MP
stay whole.
- LoRA Loader: gear -> Long names, end (the default, as before) or middle,
which keeps the checkpoint number at the end of a name in view. The
picker opens wide enough for long names and shortens the folder first.
- The frontend adds 104 px to a DOM panel's minWidth before using it as
the node's floor (LGraphNode.computeSize), so a node that opened
narrower jumped wider the moment its corner was touched: the LoRA Loader
could not go under 424. fillNodeHeight gains exactMinWidth, which
measures that padding and takes it back off, and holdVueNodeMinWidth
lends the floor to Nodes 2.0, whose corner drag reads the node's inline
min-width instead. Both nodes use them; the other panels keep their old
floors for now.
- README: the workflow-tools image shows Latent Size alone; the Seed card
is gone.
When the grown context box was bigger than the picture on an axis (a wide
or tall mask, or a small picture), fit_rect centred it and the overflow was
replicate-padded: the sampler got stretched copies of the edge pixels, and
a megapixel target spent its budget on them. On a 1184x2112 portrait with
the coat painted, only 42% of the canvas was the picture and the new part
was drawn at 0.52x.
New optional input keep_inside (card row "Stay in picture", on by
default) clamps the box to the picture on the overflowing axis before it
is fitted, so the crop holds only real pixels (0.80x on that portrait).
The helper keeps the old padding by default for existing callers; off on
the node is the old crop. Appended as the last optional input, so saved
workflows keep their values; ones whose box ran past the picture now
render a little differently.
The example's Workflow Note and the Krea 2 Outpaint Model Patch help page
said you could paint over part of the picture with the mask editor and pad
at the same time. Load Image + Pad drops the picture's alpha, its mask
covers only the new padding, and it hides the core preview the mask editor
opens from, so that never worked. Both now say the workflow only extends
the picture outward.
The help page also said a Crop For Inpaint stitcher places the reference
over the full frame. On source rectangle it falls back to where the crop
sat in the full picture, so the page now says to use whole canvas with one.
Once the readout held a time, drawReadout ran a canvas filter blur on the
graph canvas three times a frame (LED halo, digits, tenths). Chrome sends
every filtered draw through an offscreen layer the size of the whole
canvas, so the entire graph redrew about 5x slower: 60 -> ~17 fps while
panning a test graph (issue #77).
The blurred passes now go into a small cached layer (cachedLayer in
js/shared/run_timer.mjs) that is repainted only when the digits, LED,
size or zoom bucket change; each frame copies it with drawImage and draws
the crisp LED and digits directly. The readout is pixel-identical within
one colour step.
- Qwen Image 2.1 Edit + Realign: type a short edit and Qwen3-VL 8B, the
text encoder the edit already loads, writes the full instruction. The
consistency LoRA sits in a LoRA Loader row, switched off.
- LTX 2.3 Video Outpaint: the distilled LoRA and the outpaint IC-LoRA load
in one LoRA Loader instead of two core loaders. The workflow checks now
refuse core LoRA loaders in examples.
- Realign to Source: description, tooltips and help page rewritten in
plain words, with the technical detail at the end of the help page.
- Settings menus: in a short window the rows no longer squeeze together
and overlap; the menu scrolls instead.
- AGENTS.md: text people read starts with the plainest version.
An edit made at the picture's own size loses whatever the model pushed
past the canvas edge, so realigning it left an empty strip on 13 of 16
held-out edits. Realign to Source now takes an optional stitcher: given
Load Image + Pad's stitcher for the padded canvas the edit was made on,
it measures inside the picture's area only and returns just that area,
so what the model drew into the margin fills the edges.
- A drift within half a pixel of a whole-pixel shift is cut straight
out of the edit with no resampling; an edit that already lines up
comes back untouched.
- The coarse pass also tries the edit at 0.86x-1.28x, so a large zoom
is found; models often reframe a padded canvas by zooming in to fill
it, and that zoom does not count toward Max zoom.
- The report says whether the frame was cut out or warped and names the
margin each side needed when an edit ran out of picture.
Load Image + Pad gains a mirror fill, which models read as more of the
scene: over 16 edits it provoked a reframe on 2, against 7 for edge
pixels and 5 for flat gray. The Qwen Image 2.1 Edit + Realign example
now edits on a 64 px mirror margin with the stitcher wired.
resolve_lora_path followed links before checking that the file sits in
models/loras, so a LoRA kept on another drive and linked into the folder
was refused as escaping it and then reported missing, which stops the
run when the node is set to stop on a missing LoRA. Containment is now
checked on the path as written, with '..' folded but links not followed,
the way ComfyUI's own loaders treat linked files.
The Workflow Note's consistency LoRA paragraph now links the LoRA's
Hugging Face and Civitai pages where it is first mentioned, not only in
the links list at the bottom.
A LoRA Loader between the diffusion model and the reference cache loads
qwen-image-2.1-consistency.safetensors (step 1500) with the step 2000
file as an alternative row, and stops the run if the file is missing.
Qwen Image 2.1 restyles otherwise often come back about 4-5% taller and
shifted: over 36 held-out restyles the worst corner moved 24.3 px
(median) without the LoRA and 1.6 px with it.
The Workflow Note lists both files with their download links, the
README requirement row names the LoRA, and the example picture is a new
render of the updated graph.
Qwen Image 2.1 draws broad edits (watercolor, anime, oil) a few percent
taller than the source, up to about 12%, differently for every seed, and
snapping sizes does not change it. Realign to Source measures that zoom
and shift against the source and warps the edit back onto the source's
frame, at the source's size. empty_mask marks the strip the model pushed
out of view; report gives the measured zoom, shift and worst corner, or
why a frame was left as it is. The display name carries EXPERIMENTAL.
Promoted from the lab node. The measurement is unchanged (the same
matrices on 166 real Qwen 2.1 renders). The warp now samples the edit
with its edge pixels extended for every fill, so the gray and source
fills no longer draw a dark line beside the strip.
New example: Qwen Image 2.1 Edit + Realign, the Qwen 2.1 Edit graph with
the realign step after decode, its report, and before and after compares.
Validation now names source_image instead of asking for input_types.
ComfyUI hands a linked input to VALIDATE_INPUTS as None and leaves an
unwired optional one out, so a sentinel default tells them apart. That
keeps ComfyUI's own link-type check (input_types switched it off), drops
the copy of it, and a missing file is one error again instead of one per
named argument. No **kwargs, so ComfyUI's list and range checks run for
every other input.
An optional source_image socket pads an image from another node instead
of the chosen file, so a step before the pad (flattening a transparent
picture onto white, say) no longer has to be saved and loaded back.
- The wire wins over the file; validation skips the file check while it
is connected and runs core's link-type check itself, since asking for
input_types turns core's off for the node.
- A wired run hands the canvas a small temp preview (one file per node,
overwritten each run) and the true source size, so the stage draws the
last image it padded; before the first run it shows an outline.
- Card rows gain linkedBy, so the Source row dims while a wire replaces it.
- Appended and optional with no widget: saved workflows and API prompts
load unchanged. Node API snapshot refreshed.
The PR is public, so its Proof section carries only what anyone can check
from the repo: the claim, the tests and the numbers. Server URLs, workflow
folders and scratch paths go to proof.md and the report to ausboss. The
machine's dev-server rules move to the creator playbook.
Resolved: CHANGELOG keeps both bullets. The feather branch's
perfect-continuation test left rotated-and-padded out because of this
bug; with both fixes in, it covers that layout too.
Resolved:
- node_image_crop_rotate_pad.py: optional inputs are the four resize
inputs, then stitch_blend, then stitch_grow; node_api.json regenerated
with test_node_api.py --update (no breaking change against main).
- test_transform_outputs.py: the optional-inputs test expects the stitch
inputs after the resize ones.
- transform_editor.mjs: the Inpaint & Stitch section and its status line
serve the image node too (feather branch) beside the size-chain status
(this branch); the Cancel code is #73's, line for line.
- LTX example note and the Clip help page carry both branches' wording.
- CHANGELOG keeps every bullet.
Rounding the canvas to Multiple put the leftover on the right and bottom
whatever was padded, so the published Krea 2 Outpaint workflow (top and
bottom padded, Multiple 16, Budget 1.6) grew a 6 px grey strip down the
photo's right edge. Krea painted it flat, the feather opened the photo
beside it, and Stitch's colour match read it as a seam.
The leftover now joins a padded side (the far one when both are). An
axis nobody padded is scaled onto the multiple when a Budget is set (one
uniform scale) and trimmed evenly without one; the node's canvas shows
the trim as -N px. The JS preview (pad_canvas.mjs / pad_panel.mjs)
mirrors the Python plan to the pixel. Crop + Rotate + Pad keeps its old
rule for now.
On a Crop + Rotate + Pad canvas that was rotated and padded (a canvas
multiple's few pixels count), the source rectangle is the crop, which
holds the rotation's empty corners. color_match read their fill as
picture and shifted the whole fill toward it: up to 33 dE on a flat
test, 57 with a resize. The transform stitcher now carries its
generated-area mask; the seam reading skips void pixels (2 px clear of
them), and a line with no picture takes the band's global offset.
Load Image + Pad and Crop For Inpaint carry no mask and stitch exactly
as before; a pin test holds Load Image + Pad's colour-matched bands to
their old values.
fix/transform-mask-only-feather stops the transform fading the picture,
so the canvas the stitcher holds is already the unfaded one. Remove
transform_pil_layers, keep_unfaded and resize_transform_to_megapixels,
their three tests and the CHANGELOG bullet; the image node keeps its
optional resize inputs and the integer aspect maths stays.
- Rotating keeps the crop's size over the same part of the picture from
the knob, slider, number box and Reset rotation (cropForRotation).
- A readout under the picture names every step that sets the output size
(crop -> pad -> align -> resize) and warns when the resize Step
stretches the picture or Align pads fill ahead of a resize; ticking
Resize shows Megapixels and Step on the face.
- Locked-aspect crops use integer maths (_ratio_box / ratioBox), so the
size shown equals the run.
- Image Crop + Rotate + Pad's four resize inputs are optional, so API
prompts saved before them validate again.
- Video Crop + Rotate + Pad -> Clip: one Length switch replaces the Fixed
frames and Limit boxes; OUT sits on the last output frame; a connected
fixed_frames / frame_load_cap takes over the length.
- The stitcher pastes the unfaded canvas when feather > 0
(transform_pil_layers / keep_unfaded). Superseded by mask-only feather
and dropped in the next commit.
Carries a copy of the editor Cancel / discard-prompt change from
feat/editor-cancel-discard (#73), which this work was built on.
The image and video transform nodes faded the picture into the fill
colour across the feather ramp. Outpaint models read that ramp as
content (the Qwen 2.1 outpaint LoRA painted a darker band along a tilted
edge, LTX's IC-LoRA left feathered bars unpainted), and the stitcher
kept the faded pixels, so Stitch's colour match measured its drift
against them and pulled the fill toward grey (d(ab) 18.5 at feather 24 on
a straight pad, 2.6 now). Feather now shapes only the mask and the stitch
blend built from it; the picture keeps a hard edge against the fill, so a
feathered canvas is the feather-0 canvas pixel for pixel.
Image Crop + Rotate + Pad gains the clip node's optional stitch_blend
(32) and stitch_grow (0) inputs, and its editor shows the same Inpaint &
Stitch section with Show blend. The editor's live fade overlay goes with
the fade.
Every fix PR and model release now ships a failing-then-passing test,
before/after evidence, and a dev server with ready-to-run PROOF workflows,
so the owner can see a change work before merging or publishing.
The Crop + Rotate + Pad editor gains Cancel beside Save & close; Escape
now cancels. Changed edits ask first (shared js/shared/discard_prompt.mjs)
and Discard restores the node to its state when the editor opened. The
Workflow Note editor asks before Cancel, Escape or a backdrop click drops
edits, and a JSON edit carried back to the form no longer outlives Cancel.
Save Image joins the shared input_preview panel with the PREVIEW switch,
backed by a new optional preview input (display only: the file is saved
and listed in the queue either way). The embed row reads
Embed workflow: no | yes.
Krea 2's text encoder is Qwen3-VL-4B-Instruct, which has no thinking
mode. With thinking off, ComfyUI appends an empty think block and the
model often ends its turn at once, so Krea 2 Prompt from Image could
return no text. The five examples that caption with Qwen3-VL now keep it
on; the Krea notes explain it instead of blaming greedy sampling.
A native select draws its option list itself, so hovering a name gave
nothing to go on: you picked by filename alone. Load Image + Pad, Load
Video and the crop / rotate / pad editors now open a list that shows the
hovered picture, or plays the hovered clip muted with its size and
length, and filters as you type; arrow keys, Enter and Escape work.
The picker (js/shared/media_picker.mjs) only reads the combo widget's
options and writes the chosen value back, so saved workflows, undo and
the API are unchanged. Pure parts live in media_list.mjs with tests.
The mode only rewrites the seed after a queue, so the first queue after
switching from Fixed to Random reused the last seed and ComfyUI returned
the cached result. Entering Random now rolls a new seed right away, and
leaving Fixed for Step takes the first step; flipping Step's direction
leaves the number alone. Checked live on a CPU instance: a Fixed run used
12345, clicking Random showed a new seed, and the next run used it.
Adds a line before Install linking the Civitai workflows and X with a star
ask, a short For workflow creators section (Workflow Note, Image Compare,
Seed, Run Timer, Save Image) and its row in the Find a node table.
With the budget at 0 a large photo kept its full size, and on a 4 MP
canvas Krea 2 painted the new sky a shade darker, creased at the corners
and cut invented limbs at the old edge. A 16-run matrix on two sky
photos (VLM on/off, feather 64/96, full size vs 1.6 MP) and a budget
sweep put the cause on canvas size alone: 1.6 and 2 MP were clean, 2.5 MP
began to band and 1 MP went blotchy. The example now uses the 1.6 MP
budget the Klein outpaint already had; the note and thumbnail follow.
Video Crop + Rotate + Pad -> Frame had gained its four resize inputs as
required, so an API prompt saved before them stopped validating. They
are optional now; the widget order is unchanged.
tests/test_node_api.py loads the pack the way ComfyUI does and holds every
node to tests/fixtures/node_api.json: inputs and outputs may only be
appended, never renamed, reordered, removed or newly required. Its rule
tests need only the standard library and run in CI.
Load Video, Select Every Nth, Image Resize and Align Image turn the pier
clip into nine 768 x 1344 frames, and Save Image writes each with a
caption sidecar and size-and-counter names. No models.
The rowboat PNG's painted mask goes through Mask Refine, Crop For Inpaint,
LaMa Inpaint and Stitch Inpaint, with Compare against the source: the
first LaMa example that needs no other pack, only big-lama.pt.
The pier clip is cropped to a square on the Clip node, Frame Interpolate
doubles its frames with blend, and Save Video plays them at the source
rate for 2x slow motion; Select Frame and Compare check an in-between.
No models, and it runs in seconds on a CPU.
The three Crop + Rotate + Pad nodes now share one canvas row (Fill,
Feather in px, Resize) and an Open editor | Reset crop | Reset row, the
image and frame nodes report width and height, the frame node resizes,
and Load Image + Pad has a Reset padding button. Their pages still
described the old image quick row and outputs.
Help pages named the raw widget (divisible_by, max_frames, color_match)
where the card shows a label (Multiple, Limit, Tone match), so a reader
could not find the control. Each such control now reads as the visible
label with the widget name beside it, and option values show the text
on the segment (match a, hist, cover, guided).
Both badges floated above the right end of the title bar, the spot where
the frontend draws its own node-source badge, so turning them on hid one
behind the other. They sit above the left end now.
The tour's Select Every Nth sat at nth 1, doing nothing, because it could
not carry the frame rate. Load Video now decodes every frame and Select
Every Nth keeps every other one and hands the halved rate to Frame
Interpolate; the output is unchanged: three seconds, 72 frames at 24 fps.
The workflow contract now fails an example whose named widget values
disagree with the values it loads, whose id is not a random UUID, whose
saved zoom is too low to draw widget text, whose download info names a
file its loader does not select, or whose save node writes into a
folder.
The frontend saves each node's widget values twice, by position and by
name, and restores by name when its experimental Restore widget values
by name setting is on. Nine example nodes had a stale named copy: the
watermark example would have loaded a different source clip and trim
window, two MiniMax H3 samplers would have randomized their seed, and
three copies had the wrong keys. Each now mirrors its loaded values.
The 2.1.0 refresh reset Krea 2 Encode to its defaults when it wired the
prompt in, which switched VLM reference off; the example's own note, the
README and the patch's help page all describe the AnyPaint recipe with
it on. The node's tooltips and help page described only Registered
Outpaint; they now name both setups, as the card already did.
ausboss_pier_9x16.mp4 has no audio stream, so the node tour, LTX and
watermark setup cards now say Save Video keeps the source audio when the
clip has a track.
Drop cg-use-everywhere's ue_properties and ue_links/links_added_by_ue,
comfyui_mcp workflow ids and VideoHelperSuite's VHS_* settings. None of
these packs is part of the examples; extra.ds and the pack's own node
properties stay.
Fourteen examples saved a view scale between 0.41 and 0.51, under the
frontend's default level-of-detail cutoff (about 0.57), so they opened
with blank widget faces. Raise those to 0.6 and keep each offset, so the
view still starts at the graph's top-left corner.
These loaders were saved before on_missing existed, so their stored
[stack, separator, panel] values put the panel's "" into the on_missing
combo. Store the current order with the default "error".
Eleven examples saved the maintainer's local subfolders (Flux.2 Klein 9B/,
Krea 2/, LTX/, minimax-h3/, Upscale-Models/ ...), so the files the setup
cards link to showed up as "Value not in list". Every core loader now
selects the basename of its download URL, carries a matching
properties.models entry, and the Krea cards name the official
krea2_turbo_fp8_scaled.safetensors their link fetches.
The image and clip transform nodes could resize their output to a pixel
budget; the frame picker, whose single frame often feeds an image or
video model, could not. It takes the same appended resize inputs, off by
default, and its face and editor show them like the other two.
The image node had a feather on/off checkbox and no fill colour on its
face while the video nodes showed a fill swatch and a feather amount,
and only the image node had a full Reset. All three now show the same
canvas row (fill, feather in px, and the resize budget where the node
has one) and the same Open editor | Reset crop | Reset buttons.
Load Image + Pad, Image Resize, Align Image and the Clip node all hand
their width and height to the next node; Image Crop + Rotate + Pad and
Video Crop + Rotate + Pad → Frame did not, so sizing a latent from them
took an extra Image Size node. Both append width and height outputs.
A transport stream seeks to whichever packet precedes the target, not
to a keyframe, so after a seek the decoder dropped everything up to the
next keyframe: a trimmed load began late or, on a clip with a single
keyframe, found no frames at all, and the preview showed and reported
a later frame than the one asked for - frame 0 included, since a seek
to the stream's own start lands past its first keyframe.
decode_from checks the first frame out of a seek and, when it lands
after the target, seeks again from further back and from the top last.
Load Video, the clip decode and the frame previews all seek through it.
Trim windows, frame indices and preview times count from the video's
first frame (frame i at i / fps), but the decoders compared them with
the file's own timestamps. A video stream that starts late - about
1.4 s into a transport stream, after an MP4 edit list's delay, or
behind leading audio - lost frames from every window, reported shifted
frame indices that the editor wrote back into frame_index, and cut its
audio out of sync.
stream_seconds moves a decoded time onto the video clock and every
decode, preview, storyboard and audio cut now goes through it. The
metadata duration falls back to the stream's own length rather than
the container's, a late audio track is padded with silence up to its
first sample, and core's VIDEO trim is shifted onto the file's
timestamps.
The three Crop + Rotate + Pad nodes can reset their canvas from the node
face; Load Image + Pad, the pack's outpaint loader, could only be reset
by zeroing four hidden rows. A corner button on the stage clears every
unlinked side and shows only while there is padding to clear.
Load Video and the Clip node divide their fps when they skip frames,
but Select Every Nth changed only the batch, so a thinned clip played
too fast unless the rate was recomputed by hand. An optional fps socket
and an appended fps output now do that division.
Both were point-in-time reports whose status lines had gone stale and
whose evidence lived only in untracked scratch folders. What still holds
moved to AGENTS.md (registry history, example workflow rules) and
SECURITY.md (what the rules do not cover); CHANGELOG.md already records
the changes they described.
AGENTS.md: the lab sync is the maintainer's step, the no-network rule and
the link helper are written down, the node template it pointed to never
existed, validation lists all four checks, publishing follows version
changes only, and the dated registry status becomes durable history. A
new section sets the bar for example workflows. docs/adding_a_node.md
adds the PUBLIC_NODE_IDS step and the documentation a node needs, and
the brand skill matches Save Image's pill and the shared helpers.
Moving comfy_torch_device into _execution_helpers put a top-level torch
import there, which broke the standard-library test run in CI. torch is
now imported inside that one function, and its tests skip where torch is
absent.
Bug reports ask for the ComfyUI, frontend and pack versions, the install
method, the node, the steps, expected and actual results, and a workflow
or screenshot; ideas start from the workflow problem; security reports
are pointed at the private policy. .gitattributes and .editorconfig pin
what the tree already holds (LF, UTF-8, spaces), and renormalizing
changed no file. Both stay out of the Registry archive.
The five CDP scripts looked for ComfyUI at http://127.0.0.1:8188/ only;
COMFY_URL now overrides it. cdp_nav_debug.mjs says so when no ComfyUI
tab is open instead of crashing, and cdp_drag.mjs gives up on a request
after 25 s like cdp.mjs rather than waiting forever. The live-testing
notes mention COMFY_URL, give the full path of the screenshot pairs,
and say that only CDP_RELOAD=1 reloads into a fresh tab.
The validate job gains a ten-minute timeout and three steps: the ten
Python test files that need only the standard library (the registry
status test now runs among them), a syntax check of every js/**/*.js
and .mjs file, and ruff 0.16.3 with the pyflakes and syntax-error
rules. Plain `node --check` exits 0 for a broken .js file that uses
import or export, so each file is piped in with --input-type=module.
Check 4 tried .comfyignore on 22 hand-picked paths with its own
gitignore matcher. With git it now lists what comfy-cli packs - tracked
files minus the ones .comfyignore matches - and fails when anything
outside the runtime allowlist ships, when .comfyignore drops a runtime
file, when an allowlist entry, js/docs/ or example_workflows/inputs/
ships empty, or when the archive grows past 12 MB (201 files and 7.1 MB
today). A copy without its own git repository keeps the sample-path
check.
The transform output check now pins the first four outputs instead of
the whole tuple: saved workflows link outputs by slot index, so those
can never move, but a fifth output appended later is compatible. Two
new checks hold today and keep holding: every public display name ends
with the pack suffix, and every public node has js/docs/<key>.md. The
docstring lists the checks in order and describes check 4 as the code
enforces it, and the never-filled warnings list is gone.
The compile check walked every .py under the checkout, gitignored
_scratch/ ports and nested checkouts included, and py_compile wrote a
__pycache__ folder next to each file it checked. It now compiles in
memory. It and the stray-mapping scan (and its test) share one file
list: tracked plus untracked-but-not-ignored files from git, or, when
git cannot answer for the tree (an installed copy), a walk that skips
dot-folders, caches and _scratch/.
The README's images and GIFs were 7.3 MB of a 14.4 MB archive, and only
GitHub shows them; nothing in the pack loads assets/. The editor
swap-file patterns and the one for a local copy of the brand skill
matched no tracked file, and comfy-cli packs tracked files only, so
they are gone.
Comments credited IS_CHANGED with re-running Image Crop + Rotate + Pad
on a new budget and with keeping a scrub from re-running the Clip node,
but every widget value is already in ComfyUI's cache key: the first
happens anyway and the second is the frontend's fixed serializeValue.
Module docstrings named one node where two or three share the helpers,
the resize/pad plumbing has more than two copies, LaMa's hidden-input
comment sat above the optional preview block, and the inpaint helpers'
__all__ left out build_transform_stitcher and the tone helpers.
Load Image + Pad and the three Crop + Rotate + Pad nodes make stitchers
too, but Stitch Inpaint's description, its stitcher tooltip and its
wrong-input errors named only Crop For Inpaint, and the Krea 2 patch
named only Load Image + Pad. Crop For Inpaint claimed its sampling mask
is never feathered though mask_blur softens it; the Frame node's
original output mentioned a resize it cannot do; and Load Image + Pad
said its reference is not computed when unwired, when it is built on
every run.
An unused math import in the Krea 2 core, an unused os import and
stitch_blend_from_mask import in the clip tests, and result assignments
nothing read in the Save Image caption test. ruff check --select F,E9
is clean again.
_fill_tensor already returns the pad color sized to the image's
channels, with extra channels opaque, so _color_pad's own alpha branch
could never run; a test now pins the opaque alpha instead. Save Image
confines every destination to the output folder before writing, so the
preview loop's "saved outside the output folder" fallback was dead too.
CROP_NODE_ID, STITCH_NODE_ID, NODE_ID and LEGACY_NODE_ID were never
read - the mappings already use the string literals registry scanners
require - and leaving them around invites the variable-as-mapping-key
pattern that hides nodes from those scanners. NAME_MODIFIERS in the
image save helpers had no reader either.
Four helper modules each carried their own _raise_if_interrupted, three
their own _progress_bar and three their own _warn_once, and Frame
Interpolate borrowed comfy_torch_device from LaMa's module.
_execution_helpers now holds one copy of each and the helpers import
them, with the old behavior kept exactly: progress_bar is the plain bar
those loops always built (unlike frame_progress it has no guard for
unknown totals), and warn_once takes each module's own seen-set, key
and cap (none for the stitch, 64 for presets, 256 for colors).
The crop helpers' image and mask checks always said "Crop For Inpaint
expected...", including when Stitch Inpaint was handed a malformed
inpainted batch and when Load Image + Pad or a Crop + Rotate + Pad node
built its stitcher. The checks now take the calling node's name, and
each of those nodes passes its own. Crop For Inpaint's messages are
unchanged.
Video Crop + Rotate + Pad -> Clip decodes through Load Video's helper,
whose errors hard-coded "Load Video" and, when a trim would not fit in
memory, told the user to set custom_width/custom_height - inputs the
Clip node does not have. The decode now takes the calling node's name
and its own advice; the Clip node suggests every_nth, max_frames and
fixed_frames. Load Video's messages are unchanged.
Save Video shares sanitize_exact_name with Save Image, which hard-coded
its own node and input, so a rooted prefix such as "/abs/x" failed with
"Save Image: exact_name must be a relative name". The helper now takes
the node and input to name; Save Image's wording is unchanged.
_lora_helpers.py defines the tuple but nothing used it; the node spelled
the same list out again. It now reads the constant, so the two cannot
drift apart. The choices and their order are unchanged.
In skip mode a row whose file cannot be found is warned about and left
out of the stack, yet its trigger words still reached the prompt,
steering the image toward a LoRA that never loaded. apply_lora_stack can
now report the rows it skipped as missing, and the node joins words only
from the rest. Rows parked at strength 0 keep their words, as the node
docs promise, so comparing with and without a LoRA changes only the
weights. The output tooltip no longer says "comma-joined": the
separator is configurable.
With on_existing set to "error", the policy was checked frame by frame
inside the write loop, so a batch whose second name already existed
wrote the first image and its caption sidecar and only then stopped -
despite the comment promising the whole batch is validated first. The
collision check now runs for every destination, after the path checks
and before the first write, so an error leaves the folder untouched.
Save Image and core's Save Video both leave the prompt and workflow out
when ComfyUI is launched with --disable-metadata, but Save Video only
read its own save_metadata toggle, so a server whose owner had turned
metadata off still embedded the graph in every video. It now consults
the same flag Save Image does.
apply_stitch already keeps the leading frames when a video model returns
fewer than the stitcher holds (LTX keeps 8n+1, Wan 4n+1), but
stitch_blend_mask still demanded the full per-frame mask, so the node
printed "stitching the first 49" and then failed building its blend_mask
output. Both paths reproduced it: the Clip node's per-frame stitcher and
Crop For Inpaint with a per-frame mask. The mask now follows the stitch;
asking for more frames than the stitcher holds is still an error.
Stitch Inpaint takes stitchers from five nodes, not one. Load Image +
Pad's reference is always built. Seed has no convert-to-input and no
"↺" button any more. LoRA templates live in the browser, and the absorb
section listed only some of the loaders it reads. Video Crop + Rotate +
Pad → Frame has four outputs and no resize. Frame Interpolate names the
RAFT file and folder, and the extras install with ComfyUI's Python.
Add Manager and comfy-cli installs and explain updates, correct the
upgrade note (1.x lost more than LM Studio Chat), fix the optional
extras commands (installing the pack as a package fails), describe the
pack-wide tools the README never mentioned, and link the changelog,
contributing guide and security policy.
An edit after the 1.2.0 release renamed its heading to Unreleased while
adding new entries above it, so the 1.3.0 release absorbed every 1.2.0
entry. The heading goes back above the first 1.2.0 entry.
The publish workflow uploaded on any pyproject.toml change reaching main,
so a keywords or description edit sent the same version to the Registry
again, a manual run could publish from any branch, and nothing required
validation to pass first. It now compares the version with the previous
commit, runs only on main, runs the offline checks, and publishes after
they pass.
Four extensions each carried their own copy of the toast-or-console
fallback, transform upload failures used a blocking alert(), and a
failed video drop onto Load Video only logged to the console. They all
go through showToast now, so every message looks and behaves the same.
Seed and Workflow Note could copy their text; Show Text, whose job is
showing text, could only be selected by hand, and a long result shown
truncated could not be copied whole at all. The corner button copies
the complete string and reports whether it reached the clipboard.
The info card's suggested-strength bounds were bare number inputs and
the gear menu's default strength and step were typed text boxes, the
last numbers in the pack that did not scrub. The scrub control gains an
optional empty state for bounds that may be absent ("any"), and gear
menu scrub rows take their own step and precision.
The pack's DOM panels must tell ComfyUI's change tracker about their
writes, and several did not: LoRA Loader row edits other than a few
menu actions, Compare's slide / A-B mode, and Mask Refine's AUTO. LoRA
rows now record on every commit, with strength drags recording once on
release, and the other two record when they change a saved value.
The quick Reset under Image Crop + Rotate + Pad promised rotation, crop
and padding but ran the editor's full reset, which also restored fill,
feather and Align. It now resets only the shape, the same keys a source
swap resets. The editor's Reset transform tooltip names Align, which it
does reset.
Reset restored every schema default, including the entries that mirror
the open node rather than a stored preference. On the LoRA Loader that
turned separate strengths off and copied each row's model strength over
its CLIP strength; on Resolution the popup showed a latent layout and
batch the node did not have. Reset now leaves those entries as they are,
and LoRA Loader gear edits enter undo history.
In legacy exact-name mode the name field wrote the filename prefix as
well as the exact name, so clearing the field to return to composed
names left the original prefix gone. The field now writes only the
widget it shows.
A second Browse left the first popup's outside-click listener behind,
which closed the new popup on the first click inside it. Each popup now
owns its listeners, Browse toggles it, and Esc closes it.
Embed workflow is an off | embed pill like every other boolean on the
pack's cards, instead of a small switch.
Over plain http (ComfyUI on a LAN address) the page has no Clipboard
API, and the optional-chained write resolved without copying anything,
so the buttons showed a success they had not earned. One shared helper
now falls back to the copy command and says whether the text really
reached the clipboard; the buttons show a failure mark when it did not.
chainCallback keeps the earlier handler's return value, so the badge's
"consumed" answer never reached LiteGraph and every click on "?" also
started a node drag. chainHandler reports a click as consumed when any
handler in the chain did. Run Timer, which has no title bar for the
badge, now opens the same card from an "About this node" menu item.
Save Image and Save Video defaulted to an AusBoss subfolder, and every
example workflow saved into one. New nodes now save to ComfyUI's output
folder as image_... and video_..., and the examples keep their file
names without the folder. Saved workflows keep whatever prefix they
already have.
Current frontends give a node with an upload button its own onDragDrop,
which shadowed the pack's prototype hook: the frontend uploaded the file
itself and the trim and sampling values stored in an AusBoss-saved video
were never restored. The instance handler is now wrapped so video files
come to the pack first and every other drop still reaches the frontend.
When Save Video moved to a widget card, its rows lost the per-format
hiding: CRF showed for gif, ProRes and FFV1, and Metadata for gif and
WebP. The old code that toggled the classic widgets was still running
under the card and could bring them back. The card rows now follow the
format directly.
The frontend hides a DOM widget below a zoom threshold unless it opts
out. The video transform nodes mounted their panel through the one
addDOMWidget call without hideOnZoom: false, so zooming out left an
empty box. A guard test now checks every DOM widget in the pack.
Current frontends give a missing-type placeholder display widgets named
UNKNOWN, UNKNOWN_1, ... Replace with AusBoss nodes read those instead of
the saved widgets_values, so every replacement started from defaults.
Registered nodes still read their live widgets by name.
SECURITY.md states what the pack reads, writes and never does, and how
to report a problem privately. CONTRIBUTING.md covers useful bug reports
and the checks a pull request runs. Both stay out of the Registry archive.
Recreate node and Replace with AusBoss nodes each carried the same link
lookup, snapshot and rollback code, and wired links with the index-based
connect call that the Registry scan reads as a network socket. The shared
helper resolves the slots with the same checks and calls connectSlots,
which is what that call did internally.
The lookup was the pack's only outbound request and a standing Registry
finding. Its info-card button, gear-menu switch, server route, sidecar
writer and hash cache are gone. A .civitai.info sidecar already beside a
LoRA is still read, so words and links from earlier lookups or other
tools keep showing.
todo2 was a finished checklist that rode along with the 2.1.0 release
commit. The ignore files listed personal note names one by one, including
several that no longer exist; local notes now follow one rule instead.
The video transform nodes' local_path widget was read by every queued run,
so any client that can reach the unauthenticated /prompt route could make
the server open any video on the disk. Local path mode now reaches only
ComfyUI's input, output and temp folders unless ComfyUI is started with
AUSBOSS_TRANSFORM_LOCAL_PREVIEW=1, for queued runs and editor previews
alike. Tooltips, help pages, README and CHANGELOG say so; the clip node's
test fixture opts in explicitly.
Video Crop + Rotate + Pad -> Clip / -> Frame:
- The rail under the preview is a timeline: playhead scrubbing on the node
face, frame-accurate IN/OUT handles that carry the playhead, Set IN/OUT
and I/O keys in the editor; js/shared/timeline_math.mjs mirrors the
backend's inclusion rule and emits hundredth-grid seconds.
- Scrubbing no longer flickers: storyboard tiles only stand in when nearer
than the frame on stage and are decoded at their target times; the scrub
session returns the frame it already decoded instead of the next one;
served previews are cached.
- Aspect lock on the format chips (pad, lock, clear) with lockPadding in
transform_geometry; Lock aspect in the editor.
- A new source keeps fill, feather, resize, Snap and Limit and re-pads to
the lit chip; a canvas row (fill, feather, resize) sits on the node face.
- The clip node declares a black fill and feather 0 as its defaults and the
editor's Reset all follows the node's own declaration, so a fresh node or
a reset never lands on the grey, feathered canvas the LTX IC-LoRA cannot
paint; image nodes keep grey/24.
Examples:
- LTX 2.3 Video Outpaint: Select Frame -> core Generate Text writes the
positive prompt; note rewritten with the tested rules (53 renders over
six clips: only fill #000000 + feather 0 decide painting).
- Core model loaders coloured and Workflow Notes grown to fit across the
other examples.
The LoRA loader promotion (strength bars, absorb chain, moved-file
resolution, reconnect, master-toggle memory, slot-band bar), the Image
Crop + Rotate + Pad megapixel resize and quick row, the scrub-input
standard, and the dynamic README badge.
ausboss: imported loras should land under the rows already in the stack.
Upstream entries first in chain order, then downstream; LoRA patches
accumulate, so apply order does not change what the graph computes.
Strength bars behind each row's name (name doubles as a scrub surface),
the absorb-chain gear action (core/rgthree/Pixaroma/AusBoss loaders,
both directions, originals bypassed), moved-file resolution at run time
with warn-and-skip for missing files plus the reconnect button riding
ComfyUI's R refresh, folder-free row names, the amber duplicate ring,
master-toggle memory, the control bar reclaiming the slot band, + LoRA
inside the stack, and unified strengths as a hard per-node default.
Pure logic merged from the lab's lora_lab.mjs into lora_stack.mjs;
settings_menu gains action rows and persist:false; _lora_helpers gains
match_lora_name and the resolve fallback; VALIDATE_INPUTS is structural
only so a missing file can never block the queue.
shields.io's dynamic TOML badge reads the version straight out of
pyproject.toml on main at view time, so the badge cannot go stale by
construction - no third version string to bump. Preflight check 3 flips
from comparing a hardcoded number to guarding the mechanism: the dynamic
badge must be present and a hardcoded release-X.Y.Z badge fails the
check. AGENTS.md's release step 1 goes back to two version strings.
The README badge advertised 1.0.0 through two releases, and the registry
side had two silent failure modes nobody was watching:
- release_preflight.py check 3: the README release badge must name the
pyproject version, so the three places a version lives (pyproject,
AUSBOSS_JS_VERSION, badge) can never disagree again. Badge bumped to
1.2.0; README also names the real ComfyUI-floor key (requires-comfyui).
- AGENTS.md release step 4: the push-triggered publish run has failed
before (1.2.0's run died; the version only shipped via a manual
dispatch) - a red run is an unpublished release until re-run.
- AGENTS.md release step 5: verify the version AND its status on the
registry. Every version this pack has published sits in
NodeVersionStatusFlagged - the automated scan holding it for human
review - and flagged versions are invisible in Manager's Select
Version picker and set no last-update, which is exactly the empty
picker on the live install. Ask the Comfy team for review; do not
re-publish.
- Optional resize of the transformed output to a pixel budget, with core
Scale Image to Total Pixels semantics: megapixels x 1024^2, aspect
preserved, each dimension rounded to resolution_steps. The image uses
the chosen filter (lanczos/area/bicubic/bilinear/nearest-exact); the
mask always resizes bilinear so feathered edges cannot ring. The math
is mirrored py+js (scale_to_megapixels / scaleToMegapixels) and
fixture-tested on both sides; the new widgets append after the stable
V1 set so saved workflows keep loading, and IS_CHANGED fingerprints
them.
- A quick row under the node's canvas: Reset (rotation, crop, padding),
a Feather toggle that remembers the amount it turns off, and the
Resize toggle with its megapixel scrub box.
- The editor gains a Resize output section (megapixels and steps as
scrub controls, method select) and its status names the exact resized
size.
- The output size readout moved off the pixels: centered below the image,
flipping above when clipped, and showing the resize target.
- CHANGELOG entries for this round and the two commits before it.
makeScrubInput in js/shared/scrub_input.mjs is the LoRA strength box's
interaction made reusable: drag the value to scrub (dead zone keeps
clicks clicks), click to type an exact value, chevron arrows step,
ArrowUp/Down step from the keyboard, Shift always means the fine step.
Values are quantized through one choke point, and edit mode re-asserts
itself a frame after the click because the graph canvas can steal focus
mid-sequence.
The gesture math is pure and tested (tests/scrub_input.test.mjs).
AGENTS.md and the brand skill record the convention - numeric fields in
pack panels use this control, never a bare input[type=number] - and
AGENTS.md also picks up the lab shared-code vendoring pull step.
- Drag-to-reorder actually drops: the row preview's reparenting silently
released pointer capture, so the gesture now listens on the window for
its whole lifetime.
- Step arrows on every strength box (configured step; Shift = 0.01), with
range tinting carried over.
- The control bar (templates, stack toggle, count, settings) sits directly
on top of the row stack and + Add LoRA pins to the node's bottom edge;
the triggers output got its narrower name (slot links reconnect by index).
- Civitai lookup reads the whole response instead of the first TCP chunk,
so successful lookups stop dying as 'lookup failed'.
- Compare and Load Video declare their height floors WITH the DOM-widget
frame allowance, so minimum-height nodes stop shaving the caption and
trim strip; panel_layout documents the rule.
- README and the LoRA doc catch up; panel_guards comments follow the
body-as-layout-box reality.
Landing a pyproject.toml change on main now publishes to the Comfy
Registry automatically, so the working rules must say so: a Releasing
section covers the version + AUSBOSS_JS_VERSION pairing (preflight
enforced), the changelog retitle, and keeping the registry description
naming the actual nodes since Manager search matches against it. The
hard rule against casual version bumps points at the consequence, and
the brand skill reminds that a new public node gets named in the
description at the next release.
- New node: Save Image - PNG or lossless JPEG XL (optional
pillow-jxl-plugin, the pack's new jxl extra), workflow embedding on or
off (honoring --disable-metadata), an exact_name mode that saves under
exactly that filename with no counter suffix, overwrite/skip/error
collision policy, output_dir up to absolute dataset paths, a caption
.txt sidecar sharing the basename, file_path and IMAGE passthrough
outputs, and always-re-run semantics so a deleted file re-saves.
- Image Resize now only invents pixels on an explicit width+height pad
request: scale-derived modes resolve divisible_by snaps with a
sub-half-step resize instead of padding a sliver; the tour workflow
switched to an explicit 512x512 letterbox so its mask lane demonstrates
real bars.
- Registry-facing metadata: the description now names every node family
(Manager search matches against it) and the keyword list grew to match.
- Release 1.2.0: version bump, frontend version constant synced,
changelog dated, and a publish workflow (Comfy-Org/publish-node-action)
that releases to the registry whenever pyproject.toml changes on main.
- New nodes: Image Resize plus the Utility group (Text, Integer, Float,
Show Text, Math Expression, Select Every Nth, Split Batch, Merge
Batches, Free Memory) - the dependency-free gaps that let shared
workflows drop heavy packs.
- Replace with AusBoss nodes (prototype): preview-then-confirm graph
rewrite swapping known third-party nodes for pack equivalents, with a
tested translation map, per-node opt-out, and refuse-with-reason for
nodes the pack cannot stand in for.
- Load Video: single-frame FRAME picker, an honest "what will load"
label from the probed source rate, drop-a-video-to-restore. Save
Video: format-aware widgets and a file_path output. Stitch Inpaint
gained a blend_mask output, Select Frame negative from-the-end
indexing, Image Size a batch count - all appended, saved workflows
load unchanged.
- LoRA Loader: drag-to-reorder rows, hover thumbnails on rows, file
size and date in the info card.
- Appearance: flagship scheme deepened to slate-teal; the retired
bright-teal pair upgrades automatically via LEGACY_SCHEME_PAIRS.
Optional queue-completion chime, off by default.
- Image Resize invariant: pixels are only invented on an explicit
width+height pad request - scale-derived modes resolve divisible_by
snaps with a sub-half-step resize, never bars.
- Docs refreshed for six stale pages, stale text and metadata fixed
across the pack, the panel-height rule codified in AGENTS.md and the
brand skill, and ausboss_node_tour.json added - a model-free example
exercising 17 node types in one graph.
Validated: scripts/validate_nodes.py (24 files), 300 JS tests, all
Python suites under the ComfyUI embedded interpreter.
Mask Refine, LaMa Inpaint and Select Frame drew their picture into a
container pinned at 140px, so dragging any of them taller only added dead
space underneath - the same complaint already fixed for Load Video, Image
Compare A/B and Save Video.
The panel is shared, and it was pinned deliberately: while it showed a small
thumbnail of the node's INPUT, a constant height was right, and the guard in
panel_guards.test.mjs listed it under fixedByDesign for exactly that reason.
Making it show the node's own RESULT turned it into a viewport onto a
picture, which is what the other set is for, and that reclassification was
missed - so the guard kept passing while the panel it described no longer
existed.
It goes through fillNodeHeight now, with a 140px floor and no ceiling, and
declares a minimum width to both layout paths because it carries the
AUTO/MORE buttons and can no longer be exempt from that. The guard moves it
to mustGrow and says out loud that this classification is a decision to
review whenever what a panel DISPLAYS changes, not only when one is added -
verified by reintroducing the pin and watching both guards name it.
Image Compare A/B drew a chip over the top-left of the picture carrying the
resolution and a hint about how to use the panel - covering part of what you
came to look at in order to say something you need once. The resolution moved
to a caption centred under the stage, and when the two sides are different
sizes both are named there: the panel scales them to fit, so nothing else on
screen would reveal the mismatch. The empty and error states still take the
middle of the stage, where there is no picture to hide.
HOLD became an A/B toggle. Press-and-hold meant the comparison only existed
while a mouse button was down, so the two images could never be held side by
side in memory. Each click now swaps the panel and the button names the side
you are looking at; flicking between them in place is how a small difference
actually becomes visible. A workflow saved on "hold" opens on the toggle it
became rather than quietly dropping back to slide.
The example workflow was stale in ways that would have shipped to Civitai:
its overview credited SimpleWatermarkRemover - the deprecated compatibility
id, not the node the graph actually uses - still called the node Refine Mask,
and linked to github.com/auboss, which does not exist. It gained model-setup
and requirements sections, consistent node titles, and the mask settings its
single-frame test was missing: that path shipped with every Mask Refine
control at zero, so the frame it previewed was not the result the full run
would produce, which is the one thing that test exists to show.
Five things surfaced by release testing.
Node search: ComfyUI's search dialog cuts at 64 results and ranks by
[match tier, global popularity, name length], where tier 1 needs the name
to START with what was typed. Popularity is a fixed global table in which
every node from a new pack scores zero, so tier is the only lever. As
"Refine Mask" this node came 121st for "mask" and was never on screen.
Renamed to Mask Refine, which is 15th, and kept "refine mask" as an alias.
The mapping key is untouched, so saved workflows are unaffected. Aliases
alone cannot fix this: the frontend indexes them but drops them from the
tier scoring because they are a list rather than a string, so they decide
whether a node is findable, not where it lands.
Previews: Mask Refine, LaMa Inpaint and Select Frame showed whatever fed
their input, which is nothing when a segmentation node is upstream - Mask
Refine's panel was blank however the mask turned out. Each now returns its
result as a preview and the panel shows that, falling back to the input
before the first run. A node does not need to be an OUTPUT_NODE for this;
returning a ui payload is enough. ComfyUI's own preview for these nodes is
stood down so the picture appears once, in the panel, rather than also
underneath the node. That also fixes LaMa finishing a single image with an
empty panel: frame previews were only attached for batches over one frame,
on the assumption that one image would preview through the output path,
which a non-output node does not have.
Mask Refine opens on expand and blur with the other five behind MORE.
Hidden widgets keep their values and their saved positions. AUTO sets both
from the mask's size, scaled off the 8/4 hand-tuned for the watermark
workflow at 576px, because a feather is a fraction of the picture rather
than a fixed pixel count.
Save Video gains nvenc h264/h265, webm av1, ProRes mov, lossless ffv1 mkv,
gif and webp, plus pingpong and save_metadata. Every format is encoded and
read back in the tests rather than trusted from its table row. fps steps in
whole frames but stays FLOAT: an INT would refuse Load Video's fps link and
round 29.97 to 30, drifting picture off audio. mov files now carry the
embedded workflow, which they silently did not. The reload button is gone -
it only re-fetched what was already on screen.
Not done: Select Frame's preview updates on a run, not as the number
changes. Seeking to a frame needs the source fps, which the browser never
learns, so live scrubbing needs a server route - the surface Frame Chooser
was just demoted for.
Pausing the graph for an interactive pick is a bigger surface than
anything else here - a server route, a resumable pause, a panel that has
to survive a reload - and it has open issues that are not worth holding a
release for. The lab is where that belongs until they are fixed.
Removes the node, its helpers, its panel, its shared DOM-free module,
its docs and its tests, and drops it from NODE_MODULES, PUBLIC_NODE_IDS,
the panel guard list and the README. _execution_helpers stays: Load Video
and Save Video use it too.
The port lives in ComfyUI-AusBoss-Lab as AUSBOSS_LAB_FrameChooser, with
its routes moved from /ausboss/frame_chooser to /ausboss_lab/ so both
packs can be installed at once. A saved workflow using the public id
reports it as missing.
The old name reads like it transforms a clip. It takes ONE frame out of
a video and returns a single image; the editor's long timeline is there
to find that frame, not to trim a range. People reach for it expecting
video in, video out.
"grab frame", "extract frame" and "frame from video" join the search
aliases, and the description says the output is a single image up front.
The mapping key AUSBOSS_NODES_VideoCropRotatePad is untouched - it is the
workflow-compatibility contract - so saved workflows are unaffected. The
arrow is safe in a display name: names reach the frontend as JSON and are
never printed at import time, where a cp1252 console would choke. The
VALIDATE_INPUTS and alert prefixes stay plain ASCII for that reason.
Video Crop + Rotate + Pad drew its preview stretched after a resize. The
canvas is CSS-stretched to whatever box the panel is given, but
prepareCanvas() sizes the backing store from the element at DRAW time, so
a box that changes shape without a redraw shows the last frame at the
wrong aspect ratio.
The panel's ResizeObserver was created inside a `kind === "image"` branch,
grouped with the interactive drag handlers because the comment there
reasons about hit-testing. Hit-testing is only half of what it does. The
image node redrew on resize and looked right; the video node - a passive
preview with no other reason to redraw - did not.
Latent until panels started taking the node's leftover height: before
that the box could not change shape without a width change, and a width
change forced a relayout that redrew anyway.
The observer now runs for both kinds; only the drag handlers stay
image-only. Two tests hold the line - one fails if any canvas-painting
panel ships without a resize observer, one fails if this observer is
gated on node kind again. Verified by reintroducing the bug: the second
test fails and names the offending condition.
The extended region breaking up is not a tuning problem. The model
places the source spanning one whole canvas axis - pad left/right and it
spans the full height, pad top/bottom and it spans the full width - and
the reference pipeline this LoRA ships with splits anything else into
TWO passes rather than doing it in one. Padding both axes and running
once puts the placement outside what the weights were trained on.
Verified on one image, one seed, changing only the geometry: padded left
AND bottom, the dress is cut off flat and the legs below it are mush;
padded bottom only, the gown continues full length with no seam.
Load Image + Pad happily pads any combination of sides, so this is easy
to hit by accident - and the usual cause is not a deliberate second pad.
Padding only the bottom of a 720-wide source at canvas_multiple 32 rounds
the width up to 832, and an 11px sliver breaks the span exactly as
completely as a deliberate 231px pad. The warning therefore reports the
spare pixels on each axis and names the multiple, instead of just saying
the placement is wrong.
Krea 2 accepts reference latents, but they arrive with no position, so the
model treats them as a loose style hint: it borrows the look and reinvents
the content. That is the whole reason an outpaint drifts instead of
continuing the photo.
Krea 2 Encode attaches the reference latents and emits the negative from
the same node, so a turbo graph at CFG 1.0 stops carrying a second text
encode that does nothing. Krea 2 Outpaint Model Patch registers those
reference tokens into the target grid at the rectangle the stitcher
records, on frame axis 1.
Load Image + Pad gained a `reference` output - the unpadded source, fitted
to a 384px long edge and a multiple of 16, because the VAE downsamples by
8 and the DiT patchifies by 2. It is APPENDED after `stitcher`, never
inserted: a workflow stores links by output slot index, so any other
position silently rewires every saved graph.
The stitcher now records where the source sits, as source_bbox in pixels
and bbox_normalized in 0..1. Padding knows the rectangle exactly, so it
rides with the canvas instead of a parallel wire that can be left
unplugged. apply_stitch reads neither key, so an older stitcher stitches
identically.
Ported from the lab rather than copied: the lab's _krea2_helpers had its
own pad_image, feather_pad_mask, plan_pad_canvas, parse_fill_color and
build_canvas_stitcher. Duplicating the padding engine here would let the
outpaint path and the inpaint path disagree about what a stitcher is, so
the public module keeps only what is specific to Krea 2 and uses the
existing helpers for the rest.
Two departures from the lab version. The model patch declared a kv_cache
input and never read it - the cache was always on - so it is wired up now.
And it imports comfy's flux internals when you run it rather than at
startup: a core release that moves one then surfaces as an error on that
node, instead of deleting it from the menu and leaving saved workflows
reporting it missing.
The old display name led with the mechanism. "Image Compare A/B" says
what the node is first, which is what someone scanning the menu reads.
"image compare" joins the search aliases; "compare" and "a/b" stay.
The mapping key AUSBOSS_NODES_Compare is untouched - it is the
workflow-compatibility contract - so saved workflows are unaffected.
Dragging a node taller only ever widened its stage. The frontend
arranges widgets in one pass:
if (w.computeSize) -> fixed height, kept OUT of the split
else if (w.computeLayoutSize) -> joins distributeSpace(freeSpace, ...)
else -> one standard widget row
It is an else-if, so a widget declaring both is pinned by computeSize
and its computeLayoutSize is never called. Every stage, player and
filmstrip here derived that fixed height from the node's WIDTH, so the
extra height became dead space underneath. Compare was fixed by hand;
the other six had the same defect.
Adds js/shared/panel_layout.mjs with one fillNodeHeight() that deletes
computeSize and declares a floor with no ceiling - distributeSpace reads
a missing maxSize as Infinity, so the panel takes whatever is left.
Applied to Compare, Save Video, Load Video, Frame Chooser, Load Image +
Pad, Image Crop+Rotate+Pad, and the shared transform panel that Video
Crop+Rotate+Pad mounts. Removing computeSize also removed where those
nodes got their opening height, so Save Video, Load Video and Load Image
+ Pad now state a default; a saved size still wins through onConfigure.
Measured in the browser: all seven grow 1:1 with the node and clamp at
their own floor.
The panel audit now enforces it - a DOM panel is either a stage that
calls fillNodeHeight and never assigns computeSize, or is listed as
fixed by design - so a new panel cannot quietly reintroduce this.
Load Image + Pad does the same job from the same stage and starts from
the file, so the only thing the second node added was taking an IMAGE
wire - one idea holding two slots in the menu.
Deletes the node, its panel, its help doc and its stage-only shared
helpers (padEmptyStateText, findPadPreview), and drops it from
NODE_MODULES, PUBLIC_NODE_IDS, the README and the panel guard list.
The padding helpers are untouched; Load Image + Pad and Align Image both
use them. Two console labels there named the removed node, so
_fill_tensor now takes the source name - Align Image passes its own
pad_color, and a colour it cannot read is no longer reported against
padding. The BHWC and mode errors read "Padding"/"Pad mode" instead.
The on-node pad stage moves to ComfyUI-AusBoss-Lab, which has its own
AUSBOSS_LAB_PadImage.
The node produced a mask offset, grown and blurred - no contact
darkening, no perspective - so it read as a sticker halo rather than a
shadow in the scene. That is the approach, not a tuning problem, so it
comes out rather than shipping under the pack's name.
Deletes the node, its helper module, its test file and its help doc, and
drops it from NODE_MODULES, PUBLIC_NODE_IDS and the README. The colour
helper tests keep their two-callers case using Pad Image and Transform,
the real remaining call sites.
blur_mask and grow_shrink_mask stay in _mask_helpers - Crop For Inpaint
and Pad Image use both.
Six fixes found while testing every node against real workflows.
LoRA Loader: the "wrong base model" warning never fired on a modern stack.
It matched comfy's model class name against a table covering 8 of the 81
classes comfy ships, so Krea 2, Qwen, WAN, LTXV, Z-Image and friends read as
"unknown" and skipped the check entirely - an SD 1.5 LoRA on Krea 2 patched
nothing, changed nothing, and said nothing. It now measures the applied-patch
count across model and CLIP before and after each row and warns when a row
patched nothing, which needs no table, no LoRA metadata, and works on every
model family. base_model_family also stopped mining ss_sd_model_name: that is
the trainer's source filename, and reading substrings out of it labelled any
..._v1.safetensors as SD1.5 and anything containing xl as SDXL.
LM Studio Chat: reasoning returned in the server's own reasoning_content field
now reaches the thinking output. Only inline <think> tags were read, so with
gemma/qwen-style hybrids the whole reply landed in a field the node never
looked at and text came back blank. An empty answer now explains itself,
naming the max_tokens budget when the model ran out of room mid-thought.
Compare: the stage follows the node's height, not only its width. Its panel
declared a computeSize, and the widget layout gives any widget that defines one
a fixed height and leaves it out of the leftover-space split, so dragging the
node taller only added dead space under the image.
Pad Image / Load Image + Pad: a stitcher output, so an outpaint can put the
source back exactly. Padding now builds the same stitcher shape Crop For
Inpaint emits - the crop is simply the whole canvas - so the existing Stitch
Inpaint node serves both and no second stitch node has to exist. Appended
last, so saved workflows keep their links.
Settings menus: editing one row no longer reverts the others. Each save handed
back a value set rebuilt from stored defaults, silently replacing every other
row on a node whose workflow differed from those defaults.
LoRA templates popover: Save was clipped off the panel edge and each saved
row's delete x was stretched across the whole row - one blanket
.ausboss-lora-menu button rule outranked the specialized buttons inside it.
2026-08-17 20:47:27 -05:00
521 changed files with 138022 additions and 9409 deletions
description:Brand identity and design language for ComfyUI-AusBoss nodes — naming, visual language, settings conventions, frontend interaction etiquette, and the proof checklist. Load when building, porting, or restyling an AusBoss node or any pack-wide frontend feature.
---
# AusBoss node brand and design language
This skill layers the pack's *design language* on top of the hard rules in
[AGENTS.md](../../../AGENTS.md) and the admission checklist in
[docs/adding_a_node.md](../../../docs/adding_a_node.md). Read those for what is
allowed; read this for what makes a node feel like an AusBoss node.
## Identity
- Mapping key `AUSBOSS_NODES_<Purpose>` — permanent API, never renamed, and
always written as a string literal in both mapping dicts so registry
scanners (which parse, not import) can discover it.
- Display name `<Name> 🆎`; category `🆎 AusBoss/<Group>`. The category, the
`AUSBOSS_NODES_` id prefix, and the "ausboss" `SEARCH_ALIASES` entry keep
the pack searchable by name.
- Every node ships `DESCRIPTION`, input `tooltip`s, `OUTPUT_TOOLTIPS`, and
`SEARCH_ALIASES` with intent words the display name lacks ("trim", "cut",
"outpaint", "watermark").
- Namespace everything frontend-visible with `ausboss`: routes, extension
names, events, CSS classes, DOM markers, caches.
**This identity is for the public pack only.** Experiments live in the private
ComfyUI-AusBoss-Lab repo and wear a parallel grammar so the two never collide
body=$(printf 'The nightly check of the pack against ComfyUI `%s` failed.\n\n- ComfyUI: %s\n- Run: %s\n\nThe failed step in that run names what broke: a module or function ComfyUI no longer has, a node or input an example uses, or a node whose face did not come up.' "$REF" "$commit" "$RUN_URL")
ComfyUI-AusBoss is a curated collection of polished nodes designed to streamline the ComfyUI workflows I use most. Each node replaces repetitive setup with focused controls, compact graph footprints, and only the outputs needed downstream. Larger visual tasks open into dedicated full-screen editors, while experiments and one-off utilities stay out of the public pack.
I made these nodes for my own ComfyUI workflows, mostly because I wanted features I couldn't find in other packs. I use them myself, and I keep adding new ones.
## Nodes
Most of them are for image and video work. ComfyUI's core nodes already cover the basics, like seeds, sizes and prompts, so use these where they add something. A few of the things they do:
### Image Crop + Rotate + Pad 🆎
- **[Mask by Touch](#mask-by-touch):** click a thing on the picture and get its mask. Hover shows what a click would take, and **Fine edges** follows hair and fur.
- **[Mask by Name](#mask-by-name):** type "the red jacket" and get its mask and a cut-out.
- **[Image Crop + Rotate + Pad](#image-crop--rotate--pad):** turn, crop and pad a picture by dragging handles on the node, for outpainting.
- **[Stitch Inpaint](#stitch-inpaint):** after an inpaint or outpaint, everything you didn't ask to change is your original pixels again.
- **[Video Crop + Rotate + Pad → Clip](#video-crop--rotate--pad--clip):** trim a clip on a timeline, and pad or crop it to a new shape.
- **[Tiled Upscale](#tiled-upscale):** upscale a picture in tiles to the megapixels you set.
- **[Image Folder](#image-folder):** tick pictures in a folder and run the workflow on each one.
- **[Layout Boxes](#layout-boxes):** draw boxes where things and words go in the picture, for MOSS-Image.
- **[Image Compare A/B](#image-compare-ab):** a before-and-after slider, and a brush to mix the two.
- **[LoRA Loader](#lora-loader):** a whole LoRA stack in one node, each row with its own switch and strength.
- **[Workflow Switches](#workflow-switches):** turn parts of a workflow off and on from one small card.
A Load Image-style node with a compact transformed preview and a full-screen editor for precise crop, rotation, padding, fill, mask feathering, and output-size alignment. The preview is itself a live stage — drag the crop handles, padding diamonds, and rotate knob directly on the node, and open the editor when you want zoom, pan, and the numeric sidebar.
### Video Crop + Rotate + Pad 🆎
Targets an exact video frame from an uploaded input file or local path. The full-screen editor adds a long timeline, playback, exact frame steps, and the same transform controls as the image node.
### Select Frame 🆎
Returns one unchanged frame from an `IMAGE` batch using a clear one-based frame number. Out-of-range requests report the valid range instead of silently selecting the wrong image.
### Frame Chooser 🆎
Pauses the graph and shows the incoming batch as a clickable filmstrip: pick the frames to keep, or Keep all / Cancel — with digits, `A`/`N`, `Enter` and `Escape` on the keyboard. A countdown can auto-answer with the policy you choose, a reload mid-pause restores the panel, and filling `pick_list` skips the pause entirely so a chosen take reruns headlessly (your interactive picks are written back there for you). Outputs the kept sub-batch, its count, and the one-based indices.
### LoRA Loader 🆎
A stacked multi-LoRA node. Each row has an on/off pill, a searchable picker (type to filter, arrow keys + Enter to pick; browse view groups by folder and hovering shows the preview image), and strengths you can **drag left/right to scrub** (Shift for fine steps) or click to type. A header pill toggles the whole stack. The per-row info card shows the LoRA's preview image, base model, trigger words from its file metadata, a one-click Civitai lookup, or your own saved words — click words to toggle them into the deduplicated `trigger_words` output — plus an optional suggested strength range that tints out-of-range values. The bar's ▤ button saves and applies named templates of the whole stack, and a LoRA trained for a different base model than the connected checkpoint logs a console warning. CLIP input is optional.
### Align Image 🆎
Snaps width and height to a clean multiple of a number you pick — 16, 32, whatever the model wants. Qwen image models, VAEs, and many samplers behave best on cleanly divisible sizes. `resize` rescales to the nearest multiple, `crop` center-crops down, `pad` replicate-pads up, and the new size comes out as INTs for wiring into latent nodes — plus `offset_x`/`offset_y` for cropping back after sampling.
### Image Size 🆎
Reads an image's dimensions as INTs — width, height, longest edge, shortest edge — so resizes and latents key off the actual image instead of retyped numbers that drift.
### LM Studio Chat 🆎
Sends a prompt (and optionally an image) to a local LM Studio server — or any OpenAI-compatible endpoint — and returns the reply as text. Empty model name means "whatever the server has loaded", reasoning-model `<think>` blocks come out on a separate output so the text stays clean for conditioning, and the seed doubles as the re-roll knob. Chain the `history` output into another chat node's `history` input for multi-turn conversations, or fill `json_schema` to force a structured JSON reply. Errors are actionable: a refused connection says how to start the server.
### Color Match 🆎
Harmonizes an image against a reference by transferring per-channel LAB mean and standard deviation — the fix for an inpainted or upscaled region that comes back slightly brighter or cooler than the plate it sits in. Optional mask restricts the correction to just that region, and `strength` blends it back toward the original. For video, `reference_mode: first_frame` locks every frame to the batch's own first frame — the one-node flicker fix.
### Pad Image 🆎
Pads an image with a solid color, replicated edges, replicated edge pixels, or a **pillarbox blur** (a blurred, dimmed copy of the frame behind the sharp original — the standard look for reframing video to a new aspect). Also returns a mask covering exactly the new padding, ready to wire straight into an outpaint. The stage on the node is the control: drag any edge of the final rect to set that side's padding over a live preview of the input (run once to fill it in).
### Load Image + Pad 🆎
A Load Image that opens straight into an outpaint canvas: drag any edge of the final rect drawn on the node to grow that side's padding — the whole edge is the handle, per-side pixel counts ride the bands, and the badge always shows the true output size. The mask covers exactly the padding with an optional **feather** ramped inward across the seam, the canvas rounds up to a clean multiple, and a **megapixel target** rescales the source *before* padding so the mask seam stays crisp at sampler-friendly sizes. Outputs the canvas, the mask, and the final width/height as INTs.
### Drop Shadow 🆎
Drops a soft shadow behind a masked subject with signed offset, grow, blur, color, and opacity — what sells a padded or reframed composition as deliberate. A multiply blend mode keeps the backdrop's texture, and the `shadow_mask` output hands the effective shadow alpha to downstream compositing.
### Frame Interpolate 🆎
Retimes a clip by **frames per second rather than a whole-number multiple**, so 24 to 30 works as naturally as doubling. Choose a fast `blend` crossfade or optical flow for real motion. Hard cuts are detected and held rather than interpolated across, avoiding the smeared morph other interpolators produce at a scene change. Memory is bounded by planning the work first and streaming results back to the CPU.
### LaMa Inpaint 🆎
Inpaints white mask regions with a TorchScript LaMa checkpoint, preserves pixels where the mask is zero, and processes large video batches one frame at a time to keep VRAM bounded. Place `big-lama.pt` in `ComfyUI/models/lama/`; the node never performs automatic downloads.
### Crop For Inpaint / Stitch Inpaint 🆎
Inpaint only where it matters: Crop For Inpaint grows the mask's bounding box by a context factor and emits the crop, the raw sampling mask, and a stitcher; Stitch Inpaint pastes the result back with a separately feathered blend so every pixel outside the blend region stays **bit-identical** to the original. The stitcher broadcasts across a video batch, so one crop serves a whole clip. An optional `fix_edge_halo` toggle removes the rim that appears when a feathered seam gets blended twice. `target_megapixels` rescales the crop to a sampler-friendly area with a selectable `rescale_algorithm`, and the `extend_*` inputs grow the frame itself for outpainting — the new bands become part of the stitched output.
### Load Video 🆎
Loads a video as frames plus audio with `frame_count`, `fps`, `width`, `height`, and `duration` outputs, plus a lazy core `VIDEO` handle so the clip chains straight into ComfyUI's own video nodes. Its single responsive player includes a two-handle trim timeline: drag **IN** and **OUT** (shown as `h:mm:ss.s` timecodes you can type into), preview exactly that window, and optionally loop it. Only the selected window is decoded, with a memory guard that reports oversized trims instead of exhausting RAM; audio is extracted lazily only when consumed. Optional custom width/height with aspect-preserving single-side mode; `every_nth` thins long clips (the fps output divides to match) and `max_frames` caps the decode outright.
### Refine Mask 🆎
Grows or shrinks a mask, fills enclosed holes, melts jagged edges with a `smooth` control that does not feather, feathers, and rescales the extremes with black/white points — returning both the refined mask and its inverse. Optional `guided filter` and `matting` tiers refine the edge against a guide image when you install the matching extra.
### Save Video 🆎
Encodes frames — or a connected core `VIDEO` — to an H.264/H.265 mp4 or VP9 webm (all CRF-controlled) tagged bt709 (so platforms don't shift your colors), with optional muxed audio and the workflow embedded, so a saved mp4 dragged back onto the canvas restores its workflow. The responsive in-node player previews the encoded result with loop and reload controls. Wire `fps` from Load Video to preserve source timing; a connected video brings its own rate and overrides the widget. Encoding runs off the executor thread with per-frame progress, so a long export never freezes the UI.
### Compare 🆎
A/B any two images: slide mode sweeps B over A under your pointer with a seam line, hold mode shows B while pressed. Passes image A through, so it sits mid-graph without rewiring.
The two transform nodes return exactly:
1. `image` — transformed ComfyUI `IMAGE`
2. `mask` — generated-area `MASK`, including source transparency, rotation corners, and padding
The operation order is always **rotate → crop → pad**. New sources start with zero rotation, full crop, zero padding, and a canvas multiple of `1`.
The **?** in a classic node's title bar opens a short card with its inputs and outputs. In Nodes 2.0, right-click the node and choose **About this node**. Every node also has a longer help page in ComfyUI's node help panel. I post the workflows I make with them on [Civitai](https://civitai.com/user/AusBoss) and [X @Zanzibased](https://x.com/Zanzibased), and videos of them on [YouTube @ausbossdev](https://www.youtube.com/@ausbossdev). Can't open Civitai? All my workflows are on [my Discord](https://discord.gg/RwVxY9Fuaj) too. If a node saves you time, a star on the repo helps other people find it.
## Install
In **ComfyUI-Manager**, open the Custom Nodes Manager, search for **AusBoss** and install it. From the command line, `comfy node install ausboss-nodes` does the same. To get the newest changes first, clone the repository instead:
Restart ComfyUI, then search for `AusBoss`. There are no additional Python dependencies; the pack uses Pillow, NumPy, Torch, and PyAV already distributed with ComfyUI. LaMa Inpaint additionally needs a TorchScript `big-lama.pt` checkpoint in `ComfyUI/models/lama/`.
Restart ComfyUI and search the node library for **AusBoss**.
## Model Links
Install it one way, not both. Manager puts the pack in `custom_nodes/ausboss-nodes` and a clone in `custom_nodes/ComfyUI-AusBoss`. They are the same pack, so if both folders are there, delete one. The console says which copy it skipped.
**lama**
After an update, through Manager or `git pull`, restart ComfyUI and hard-refresh the browser with **Ctrl+Shift+R**. Otherwise the browser keeps the old JavaScript, and the pack warns you about it. Manager can install a version or two behind while the Comfy Registry reviews a new release. To get the newest one, pick it in Manager's version list, or update by hand: delete the pack's folder in `ComfyUI/custom_nodes` and clone the repository as shown above.
The pack uses the Pillow, NumPy, Torch and PyAV that come with ComfyUI. Each example lists the models it needs on its Workflow Note. LaMa Inpaint also needs [`big-lama.pt`](https://github.com/Sanster/models/releases/download/add_big_lama/big-lama.pt) in `ComfyUI/models/lama/`. The pack never downloads weights by itself.
## Model Storage Location
### Upgrading from 1.x
```text
📂 ComfyUI/
└── 📂 models/
└── 📂 lama/
└── big-lama.pt
2.0 removed **LM Studio Chat** (use **Text** for a fixed prompt) and Align Image's `offset_x` and `offset_y` outputs. Math Expression's `a`, `b` and `c` are sockets now instead of typed boxes, so put constants in the expression itself. 1.2 removed **Drop Shadow**, **Pad Image** (use **Load Image + Pad**) and **Frame Chooser**. Save Image writes only inside ComfyUI's output folder. The [changelog](CHANGELOG.md) has the details.
| Section | What's in it |
|---|---|
| [Image nodes](#image-nodes) | Rotate, crop and pad; resize, align and measure; upscale in tiles; run a whole folder; match colors; realign edits; compare; save |
| [Video nodes](#video-nodes) | Load and trim, pick a frame, transform a clip, change the frame rate, save |
| [Mask and inpaint nodes](#mask-and-inpaint-nodes) | Mask a thing by clicking it or typing its name, refine masks, remove objects with LaMa, crop around a mask and stitch the result back |
| [Models and conditioning](#models-and-conditioning) | LoRA stack, Krea 2 prompts and references |
| [Example workflows](#example-workflows) | Ready-made workflows, each with a setup card |
| [For workflow creators](#for-workflow-creators) | Setup cards, before and after, repeatable seeds, measured run times |
| [Pack-wide tools](#pack-wide-tools) | Help cards, node colors, snap into line, recreate and replace, run status, completion sound |
## Image nodes
### Image Crop + Rotate + Pad
Rotate, crop and pad a picture by dragging handles on the node. Cyan squares crop, orange diamonds pad, and the green handle rotates. It rotates first, then crops, then pads. The full-screen editor adds exact sizes, zoom and pan.
Open **Crop, pad & resize** under the preview and tap a ratio to pad the picture to it, or to crop it with **Fit** set to crop. A lit ratio is the shape the canvas has right now: drag a handle to another shape and it goes dark, and the row says Custom. The padlock at the end of the row keeps the shape while you drag, and the button at the start turns the shape between portrait and landscape. **Reset crop** brings back the whole picture, and **Divisible by** makes the width and height divide evenly by a number such as 8, 16 or 32, for models that need it. Feather and output resizing are on the node and in the editor.
It returns the `image`, a `mask` of everything the model paints (padding, see-through parts of the source and the corners a rotation opens up), a `stitcher`, the untouched `original`, the output `width` and `height`, and a `prompt_image`. Wire the stitcher into **Stitch Inpaint** after the sampler to put your original back. See-through parts of a PNG, such as a cutout's missing background, are filled like the padding and painted; `prompt_image` and `original` show them as white. Give `prompt_image` to whatever writes your prompt, so it describes a real backdrop instead of the gray fill. **Load Image + Pad** has more fill choices and a separate reference output.


Krea 2 Rotate + Outpaint turns a photo, pads it and paints the new area in one pass. [Try the Krea 2 Rotate + Outpaint example →](example_workflows/Krea%202%20Rotate%20%2B%20Outpaint%20%28AusBoss%29.json)
### Load Image + Pad
Makes a canvas for outpainting: drag the edges out to add room around a picture. The Source list shows the picture under the pointer. The new area can be a solid color, the average color of the nearest edge, the edge pixels smeared outward, a blurred copy of the picture, or a mirror of it. The card also has a seam feather, a canvas multiple and a megapixel budget, and **Exact padding** opens one control per side. A budget of 0 keeps the picture's size, and anything higher resizes the picture before padding. A picture wired into `source_image` is padded instead of the chosen file.
It returns the padded `image`, a `mask` of the padding, `width`, `height`, a `stitcher`, and a smaller unpadded `reference` for the model. Wire the stitcher into **Stitch Inpaint** after the sampler, and everything outside the seam stays your original picture.
The Klein 9B Outpaint example pads its picture with Load Image + Pad. [Open the Klein 9B Outpaint workflow →](example_workflows/Klein%209B%20Outpaint%20%28AusBoss%29.json)
### Image Folder
Pick a folder, tick the pictures you want in a thumbnail gallery, and run the workflow on each one. **All in one run** does every picked picture with one press of Run. **One per run** loads one picture each run and steps to the next by itself, so a picture that fails only skips itself. After the last picture it stops with a message, or starts over at the first and keeps going. It gives each picture's mask, its file name for saving the result under the same name, its number and how many there are.
**Browse** lists the folders inside ComfyUI's input or output folder. A folder from anywhere else on your computer is brought in: drop it on the node, or choose it in your file explorer, and its pictures are copied into the input folder. A really big folder asks first. The node reads nothing outside those two folders.
### Image Resize
Resizes to a width and height, a longest or shortest edge, a megapixel count, or a scale factor. It can fit inside the size, stretch to it, fill it and crop the rest, or pad with a color you pick. New nodes start in megapixels mode, and saved workflows keep their mode. The card shows only the controls for the chosen mode, and width and height each take their own link.
An optional mask is resized the same way. It returns `image`, `mask`, `width` and `height`. Padding shows up white in the mask, and with no mask wired in, the rest of the mask is black. `divisible_by` rounds the size to a multiple, and `interpolation` picks the resize filter.
### Tiled Upscale
Sets the size of the finished picture in megapixels and cuts your picture into tiles a model can redraw one at a time. A picture that fits in one tile stays whole, and a small picture is not redrawn far past the detail it has. Send the tiles through your sampler, then into Tiled Upscale Stitch with the stitcher.
Megapixels is the one control to set. Under More are the tile size, the overlap, the grid multiple and the redraw limit, and an upscale model can be plugged in. It returns `tiles`, `stitcher`, `before` (your picture enlarged the plain way to the finished size), `width`, `height` and a `report` that says what was done.
### Tiled Upscale Stitch
Puts the redrawn tiles from Tiled Upscale back together, blends the joins and brings the picture to the size you asked for. Keep colors matches each tile to the same tile of your picture first, so the tiles agree with each other and grain in an old photo does not push the colors.
The Qwen Image 2.1 Upscale + Restore example redraws a small or worn photo sharp at the megapixels you set, in tiles above about 2. [Try the Qwen Image 2.1 Upscale + Restore example →](example_workflows/Qwen%20Image%202.1%20Upscale%20%2B%20Restore%20%28AusBoss%29.json)
### Align Image
Rounds a picture's size to a multiple you choose, by resizing, cropping or padding. Use it when the next model needs sizes that divide evenly and you don't want to type a target size. Crop and pad have anchors for where to cut or add, and padding can repeat the edge pixels or use a solid color. It returns the picture with its width and height.
### Image Size
Reads a picture's `width`, `height`, `longest_edge`, `shortest_edge` and batch `count` as whole numbers. Wire them into a resize, a latent, a frame count or Math Expression, so the rest of the graph follows the source's size. After a run the node shows each number beside its output.
### Color Match
Matches a picture's colors to a reference, with LAB, RGB, MKL or histogram transfer. Strength sets how much, and an optional mask (with invert) limits where. With `reference_mode` set to first frame, every frame of a batch matches the batch's first frame. That can cut down color drift in a clip, but it does not steady motion.
### Realign to Source (EXPERIMENTAL 🧪)
Lines an edited picture back up with the original. Qwen Image 2.1 often draws an edit, especially a style change, a little bigger or shifted: up to about 12% taller, by a different amount every seed. Wire in the edit and the picture it was made from. The node measures how far the edit moved and moves it back, at the original's size. It fixes the zoom and shift of the whole picture, not shapes the edit redrew in a new place, so the result lines up closely rather than pixel for pixel.
For the best result, pad the picture with Load Image + Pad's `mirror` fill before the edit and wire its `stitcher` in. The edit gets room to move, and you get back just your picture's area with real picture at the edges. Without that margin, an edit made at the picture's own size loses a thin strip the model pushed out of view. `empty_mask` marks that strip so you can crop or inpaint it, and **Empty fill** picks what it shows in the meantime.
**Fit** is `zoom + shift` (how Qwen edits drift) or `affine`, which also fixes a slight tilt. When the drift is a whole number of pixels, the node cuts the picture out as it is, with no resizing. `report` gives the measured zoom and shift, and the margin a side needed when an edit ran out of picture. A picture it can't measure passes through at the source's size, and the report says why. [Try the Qwen Image 2.1 Edit + Realign example →](example_workflows/Qwen%20Image%202.1%20Edit%20%2B%20Realign%20%28AusBoss%29.json)

The same watercolor edit without and with Realign to Source: 61 px off before, 0.6 px after.

Qwen drew this watercolor 5.6% taller, and Realign to Source lined it back up with the original.
### Image Compare A/B
Compares two pictures with a slider, or flips between them whole. Nothing covers the picture, and its size shows underneath. The panel grows with the node, and images A and B pass through, so you can put it anywhere on an image wire. **Erase** mixes the two by hand: rub parts of one picture away with a brush so the other shows through, then save the mix as a PNG under a name you type, with a layered PSD for Photoshop or GIMP if you want it.
### Save Image
Saves PNG, lossless WebP or lossless JPEG XL, with the workflow embedded if you want it. Pick a folder inside ComfyUI's output folder or browse its subfolders, edit the file name, and the path preview shows where the file will go. Chips add a counter, the date, the time, the size or a batch number to the name. Name tokens work as in ComfyUI's own Save Image: `%date:yyyy-MM-dd%`, `%width%`, `%KSampler.seed%`.
Link `filename` to keep a name from upstream, or `caption_text` to write a matching `.txt` next to each picture. The counter picks a name that isn't taken yet. Turn it off to reuse the same path on purpose. JPEG XL needs the optional `pillow-jxl-plugin` extra. The [help page](js/docs/AUSBOSS_NODES_SaveImage.md) has the full naming and compatibility notes.
Turn on **steps** to watch the picture form in the node while it is being made, with a **STOP** button for a run that is going wrong. It is off by default.
## Video nodes

Here LTX 2.3 Video Outpaint takes a square clip to 9:16 and stitches the original frames back in.
The example keeps the clip's audio when there is any. The outpaint LoRA needs a pure black fill, since gray or white bands come back flat. With a black canvas it works at any aspect ratio and on any side. [Open the LTX 2.3 Video Outpaint workflow →](example_workflows/LTX%202.3%20Video%20Outpaint%20%28AusBoss%29.json)
### Load Video
Upload a video, or pick one from the Source list, which plays the clip under the pointer. Trim it with IN and OUT handles or typed timecodes. The player plays the part you kept and can loop it. Width and height resize while decoding, and setting only one keeps the shape. `every_nth` skips frames and lowers the reported fps so the timing stays right, and `max_frames` caps how many frames load.
It returns the frames, audio, frame count, fps, width, height, duration and a core `VIDEO`. Only the part you kept is decoded, and it checks memory before a large load. When you save the frames, wire its fps into the save node.
### Load Audio
Upload a sound file, or pick one from the Source list, and the node shows it as a wave. Drag the IN and OUT handles to keep only the part you want, or type the times. Press play to hear just that part. Set **Max length** to the longest sound the next step can take, such as a video's length: anything past it turns red and is cut off, so you see what comes out before you run. Click the red length next to OUT to move OUT to the cut.
It returns the `audio` you kept and its length in `seconds`. Only that part is decoded. [Help page](js/docs/AUSBOSS_NODES_LoadAudio.md).
### Video Crop + Rotate + Pad → Frame
Picks one frame from a video, then gives you the same rotate, crop and pad controls as the image node, plus an optional megapixel resize. Scrub to the frame with the playhead on the node; the editor adds frame-by-frame stepping and playback. It returns the transformed `image`, a `mask` of the added area, a `stitcher`, the chosen `original` frame, and the output `width` and `height`. For a whole clip, use the Clip version below.
### Video Crop + Rotate + Pad → Clip
Applies one rotate, crop and pad to every frame of a trimmed clip. It has the source picker, a timeline with a playhead and frame-accurate IN and OUT handles, frame skipping, a frame limit, the transform handles and output resizing. While you drag a trim handle, the node shows the exact first or last frame the run keeps. The bright part of the selection is what gets output.
Fill, feather and the resize budget are in **Crop, pad & resize** below the timeline, and a new clip keeps them. That matters for video outpainting, because the black, hard-edged canvas the model needs survives a source swap. A fresh Clip node starts with that canvas, and the editor's **Reset transform** goes back to it. **Frames for** trims the end to the frame count the next video model takes: LTX (8n+1) or Wan (4n+1). Tap a ratio to pad the clip to it; a new clip is padded to the lit ratio too. The padlock keeps the shape while you drag a handle.
It returns the frames, mask, audio, frame count, fps, size, duration, a `stitcher`, and the chosen `original` frames before the transform. **Inpaint & Stitch** in the editor sets the protected source area and previews the blend. After a video outpaint, wire the stitcher straight into **Stitch Inpaint**, with no Crop For Inpaint in between. It works in chunks to keep memory down.
**Fixed frames** sets an exact output length: 120 frames at 24 fps is five seconds. Drag either handle, or drag the highlighted band to move the whole window. Type the count, or link a frame count into `fixed_frames`. 0 turns the fixed length off. The frame limit still caps the length.

The Clip node's editor, from the video outpaint example.
### Select Frame
Picks one frame from a batch, unchanged: `1` is the first, `-1` the last and `-2` the one before it. 0, or a number past the end, stops with an error that gives the valid range. The preview switch shows the frame, or hides it and skips writing the preview file.
### Frame Interpolate
Changes a clip's frame rate by making in-between frames. You set the source and target fps, so 24 to 30 works as well as doubling. The blend method crossfades frames, and optical flow uses RAFT weights you put in place yourself. At a hard cut it holds the frame instead of blending two unrelated shots. It works in chunks to keep memory down.
It returns the new frames and their fps. It doesn't touch audio, so carry the source audio to Save Video separately. The [help page](js/docs/AUSBOSS_NODES_FrameInterpolate.md) covers the weights and timing.
### Save Video
Saves a frame batch or a core `VIDEO` as MP4 (H.264 or H.265), WebM (VP9 or AV1), ProRes MOV, lossless FFV1 MKV, GIF or WebP. Which encoders you get depends on your FFmpeg and PyAV build, and H.264 and H.265 come in CPU and NVENC versions. The videos carry color tags, and optionally audio and the workflow.
**Ping-pong** plays forward and then back without repeating the turnaround frames. A wired `VIDEO` brings its own frame rate; otherwise wire fps from Load Video or Frame Interpolate. The player on the node shows the saved file, and **Preview steps** shows the clip's first frame while it is being made, with a **STOP** button. Drop a saved video back on the canvas to load the workflow inside it, for the formats that can hold one. The [help page](js/docs/AUSBOSS_NODES_SaveVideo.md) has the format and audio details.
## Mask and inpaint nodes

Krea 2 Inpaint Masked turns the painted fur coat into a leather jacket, using Mask Refine, Crop For Inpaint and Stitch Inpaint. [Open the Krea 2 Inpaint Masked workflow →](example_workflows/Krea%202%20Inpaint%20Masked%20%28AusBoss%29.json)
### Mask by Name
Type what to find in a picture, like "the dog" or "the red jacket", and get its mask. Nothing to paint. The node shows what it found, tinted on the picture, with how sure it is, and **FIND** runs only this node so you can check the mask before the rest of the workflow runs. When it finds nothing it stops the run with a plain message, so nothing after it runs on an empty mask; a setting passes an empty mask on instead.
It also returns the thing cut out on a see-through background, and how many things matched. Under **More**: how sure SAM 3 must be, fill the holes in the mask, and free SAM 3 from VRAM after the search. If you build a workflow that loads a big model after this node, turn **Free VRAM** on. The search is ComfyUI's own SAM 3: wire `model` and `clip` from a Load Checkpoint that loads the SAM 3 file. Needs ComfyUI 0.20 or newer, which has SAM 3 built in.
The Mask by Name example finds a thing by its name and saves its cut-out and its mask, with a Workflow Switches card to skip either one. [Open the Mask by Name workflow →](example_workflows/Mask%20by%20Name%20%28AusBoss%29.json)
### Mask by Touch
Click a thing on the picture and get its mask. No name to type and no MaskEditor: the picture is on the node. Move the pointer over it and the thing a click would take lights up, with its name beside the pointer: the jacket, not the whole person. Click to pick it, and click more things to add them. What is lit in teal is what the mask will be.

When a click could mean more than one thing, the node shows the pick with two arrows, like **‹ jacket · 1 of 3 ›**: an arrow swaps it for the next thing at that spot. A stroke across two things, like both shoes, takes both. Ctrl-click a thing to take it away again.
To fix the mask by hand, switch **Pick | Paint | Erase** (or press W, B or E, as in Photoshop). The brush has a size and a soft edge, set with **[** and **]** or by holding Alt and the right mouse button and dragging, and it shows under the pointer as it will paint. Hold **Ctrl** for the opposite: erase while you paint, paint while you erase.
Every area has a chip under the picture: a thing you picked, or an area you painted. Paint that touches an area joins its chip, and erasing makes none. A chip's cross takes that area away, a click selects it so what you paint joins it, and **all 3** on a named pick takes every thing of that name. The field at the end of the row takes a name you type, for things that did not light up.
**Subject** picks the picture's main subject in one click, and **Background** everything else. **Fine edges** is for hair and fur: switched on, the outline of the subject is followed strand by strand, on light and dark backgrounds, and the cut-out loses the colour of the old background. All three use ComfyUI's own background-removal model (`birefnet.safetensors`, ComfyUI 0.21 or newer); the help page says where to get it.
**Grow** and **Soften** show on the picture while you drag them. Under **More**: Smooth, Fill small holes, Invert, what to do when nothing is picked, and Free VRAM. The eye beside Undo and Clear hides everything else to show the result, as a cut-out or as the mask.
It also returns the picked area cut out on a see-through background, and the names of what was picked as text. The search is ComfyUI's own SAM 3: wire `model` and `clip` from a Load Checkpoint that loads the SAM 3 file. Needs ComfyUI 0.20 or newer, which has SAM 3 built in. The [help page](js/docs/AUSBOSS_NODES_MaskByTouch.md) has the keys and the details.
The Mask by Touch example loads a picture, lets you click what you want, and saves its cut-out and its mask, with Fine edges on. [Open the Mask by Touch workflow →](example_workflows/Mask%20by%20Touch%20%28AusBoss%29.json)
The Qwen Image 2.1 Inpaint example uses the same click to change one thing: click it, say what it should become, and only that part is repainted. [Open the Qwen Image 2.1 Inpaint workflow →](example_workflows/Qwen%20Image%202.1%20Inpaint%20%28AusBoss%29.json) · [Watch it on YouTube →](https://youtu.be/iMgaiXoB0v4)
### Mask Refine
Grows or shrinks a mask, blurs it, fills holes, smooths jagged edges, and sets black and white points. The card shows Expand and Blur, and **More** opens the rest. **AUTO** picks expand and blur values from the mask's size. The optional preview shows the result.
It returns the refined mask and its inverse. The `guided filter` and `matting` edge modes use a guide picture and each need an optional extra (see [Optional extras](#optional-extras)). The default, `off`, needs nothing extra.
### LaMa Inpaint
Removes what is under the white part of a mask and fills the hole from the surroundings, using a local LaMa model. Pixels under black stay as they are. One mask can cover a whole batch, and video frames go through one at a time to keep VRAM use down. The preview can show frames as they finish, or be switched off.
Put `big-lama.pt` in `ComfyUI/models/lama/`. It works one frame at a time, so a hard texture in a video may need more cleanup across frames. The old `SimpleWatermarkRemover` node id still loads, so older workflows keep working. [Try the Simple Video Watermark Remover example →](example_workflows/Simple%20Video%20Watermark%20Remover%20%28AusBoss%29.json)
### Crop For Inpaint
Crops the masked area plus some of what's around it, and can resize the crop to a target size or megapixel budget. It returns the cropped `image`, the `mask` the model paints in, and a `stitcher`. The card shows the usual mask and blend settings, and target size and canvas extension fold behind a disclosure.
The mask and the blend are separate. Growing or blurring the mask changes what the model can repaint, and the blend margin changes how the result joins the original. Raise **Context** or **Extra context** when the model needs to see more around the area. **Stay in picture**, on by default, stops the crop at the picture's edges. The `extend_*` controls grow the canvas for outpainting.
### Stitch Inpaint
Puts a generated crop or outpaint back into the original, using the stitcher from Crop For Inpaint, Load Image + Pad or any Crop + Rotate + Pad node. Everything outside the blend area keeps the stitcher's original pixels exactly. A video stitcher carries every original frame.
For a turned picture, or an outpaint with Tone match off, set **Seam** to **blend in** in the card's gear menu. It fades the model's picture into yours with no tone shift. **Tone match** evens out color steps at the seam. **Fix edge halo** cleans up a rim that got blended twice. It needs the optional matting extra, and without it the node warns and stitches normally. The [help page](js/docs/AUSBOSS_NODES_StitchInpaint.md) explains the stitcher and batches.
## Models and conditioning
### LoRA Loader
A whole LoRA stack in one node. Each row has an on switch, a searchable picker for the file and a strength you scrub. The strength bars share one scale, so the rows are easy to compare, and switching off the strongest row rescales the rest. Model and CLIP strengths can be split, and the CLIP input is optional.
The toolbar has the stack toggle, saved templates, reconnect and settings. The toggle turns the whole stack off, and back on to the rows you had on. **Absorb chain LoRAs** in the settings menu moves a chain of other LoRA loaders into this stack, in the order they applied, repeats included. It bypasses the old loaders only when every MODEL, CLIP and other connection can be kept. A shared branch or linked stack settings leave the chain alone.

Each LoRA gets an info card from the file's metadata, a `.civitai.info` file next to it, and words you save yourself. The card's **Fetch Civitai info** button looks the file up on Civitai (your browser asks civitai.com, only when you click) and saves the answer as that `.civitai.info` file. Files Civitai doesn't know, such as unpublished or hidden models, come back as not found. The trigger words you pick come out of the `triggers` output. The same click saves the LoRA's picture from Civitai next to the file, so hovering the LoRA shows it (PG-13 or milder unless you allow more in the gear menu; a picture you already have is never replaced).
In the **+ LoRA** list, tick several files to add them at once. Rows can be gathered into groups, like layer groups in an image editor: each group folds away, and has its own switch that turns all its LoRAs off while each one keeps its own setting. A Workflow Switch can turn a whole group on and off.
When a file has moved, the node finds it by name if only one file matches. A missing LoRA that is switched on stops the run by default. Turn off **Stop on missing LoRA** to warn and skip it instead. A LoRA that changes nothing in the model gets a warning with its file name.
### Krea 2 Encode
Krea 2's positive and negative prompts in one card. Connect a VAE and reference pictures, and it adds them as references too, resized for the model first. **VLM reference** sets whether the text encoder also looks at them. Leave the references empty for plain text to image. For an outpaint, connect the pad node's mask too: the model then sees the new area in your picture's own colour instead of gray, so it paints it rather than keeping a gray frame.
The prompt box takes several lines and a linked text. The [help page](js/docs/AUSBOSS_NODES_Krea2Encode.md) covers preparing references. With an outpaint LoRA, copy the wiring from the matching example.
### Krea 2 Outpaint Model Patch
Tells Krea 2 where a reference picture sits on the canvas. It's made for Krea 2 outpaint LoRAs, and the two that exist want different placements. Put it after the LoRAs and before the sampler, and connect a stitcher and the reference conditioning from Krea 2 Encode.
The card has **Reference** placement and **KV cache**. The placement has to match the LoRA:
- **Whole canvas + AnyPaint:** the padded picture is the reference, VLM reference is on, the pad mask goes into Krea 2 Encode's `mask`, and a masked starting latent keeps the known pixels. Several sides can extend in one pass. The Krea 2 Outpaint example works this way.
- **Source rectangle + Registered Outpaint:** use the unpadded reference with VLM reference off. The source has to span the whole canvas in one direction, so extend left and right, or top and bottom, in each pass, including any padding added for rounding.

Here Krea 2 Outpaint takes a 16:9 photo to 4:5 in one pass. [Open the Krea 2 Outpaint workflow →](example_workflows/Krea%202%20Outpaint%20%28AusBoss%29.json)
The patch relies on ComfyUI's internal attention code. Its [help page](js/docs/AUSBOSS_NODES_Krea2OutpaintModelPatch.md) explains both setups and what happens without reference conditioning.
## Workflow utilities
Core ComfyUI has its own nodes for most of these jobs, and they work fine. These versions add the extras described under each one.
### Latent Size
Sets a canvas size. Pick landscape or portrait, a ratio and a megapixel budget, or drag the canvas handles. Orientation stays separate from the ratio, even on square. Typed sizes stay exact, and how drags snap is adjustable. It was called Resolution Master before 2.4.0.
It returns `width`, `height` and an empty image latent. The gear picks the latent layout (4, 16 or 128 channels) and the batch size. The latent's size rounds down to whole latent cells, so pick sizes that suit the layout when you wire the latent to a sampler. For video, use `width` and `height` with the model's own video latent node.

### Layout Boxes
Draw boxes on a canvas and say what goes in each one: a thing, or words to write in the picture. Wire `elements` to MOSS-Image Layout Prompt and the picture puts each thing where its box is. It does what ComfyUI's own Create Bounding Boxes does, with the same inputs and the same outputs from the same boxes, and its canvas also works in the classic node look.
Drag to draw a box, drag it to move it and its handles to size it; under the canvas each box gets **object** or **text**, its words and up to five colors. A picture on `background` shows under the boxes and sets the canvas size, `width` and `height` can come from Latent Size, and boxes on the `bboxes` wire fill the canvas when the workflow runs. **Grid** snaps the boxes as you drag them, starts off over a picture and takes its size from the gear, **Layouts** starts from a ready arrangement, the arrow keys move the chosen box one pixel at a time (ten with Shift), and Delete and Ctrl + D remove and copy it.
The `mask` output is white inside every box: load a picture, draw a box over the words on a sign, and an inpaint chain can write new words there while the rest of the picture stays as it was. For words at a slant, drag the round handle over a box to turn it: the mask turns with it, and a turned text box tells the model its slant.

### Seed
One seed for several samplers. **Random**, **Fixed** and **Step** set what happens after each run. Switching to Random, or from Fixed to Step, changes the seed right away, so the next run is a new one. **New seed** rolls a value and keeps it. **Use last run** brings back the seed the last run actually used. The history menu keeps the last eight seeds and saves them with the workflow.
### Select Every Nth
Keeps every nth picture of a batch, after an offset that counts from 0. With nth 2, offset 0 keeps frames 1, 3 and 5, and offset 1 keeps 2, 4 and 6. The order stays the same. Wire the batch's fps in, and the `fps` output is divided by nth, so the thinned clip still lasts as long.
### Split Batch
Splits a batch after a frame number, counting from 1. Output `a` ends with that frame, and `b` gets the rest. Each side needs at least one frame, so a split point that breaks that stops with an error that gives the valid range.
### Merge Batches
Puts batch `b` after `a`. When the sizes match, nothing is resized. When they differ, it resizes to a's size or b's size, or stops with an error, whichever you pick. Both need the same number of channels.
### Text
A text box that grows with the node and sends its text, unchanged, down a `STRING` wire. Use it for a prompt, caption, path or other text that several nodes share. Spaces and line breaks are kept. Ctrl + Up and Ctrl + Down change the weight of the selected words, as in ComfyUI's own prompt boxes.
### H3 Prompt Writer
Type what happens, one line for each moment, and leave a blank line to cut to a new place. Connect up to four pictures and give each one a job: a person, a place, an outfit, a thing or a style. Copy the moves from a video, keep a clip going, or add a voice or a song. A timeline on the node shows when each moment plays before you run it. It needs no extra model and you never see H3's prompt format. Connect a small text model and it fills in a one-line idea.
**Change something** edits the named part of a video while keeping its person and sound. For a person swap, an optional `@1 looks like ...` line describes the connected replacement. Jobs that keep the source clip's length show **From the clip**; the `keeps_clip_sound` output helps a workflow choose the source audio.
### Integer
One whole number (`INT`) on a scrub control, for sharing a size, count or other whole number across nodes. Like every number box in the pack, it takes a typed sum: `1024*1.5`.
### Float
One decimal number (`FLOAT`) on a scrub control, for sharing a strength, scale, frame rate or duration across nodes.
### Math Expression
Works out a number from linked `a`, `b` and `c` inputs, each an integer or a decimal. An input left unwired counts as 0. It handles arithmetic plus `min`, `max`, `abs`, `round`, `floor`, `ceil` and `sqrt`, for example `a * 2` or `ceil(a / 32) * 32`. It returns a float and a rounded integer, and halves round away from zero. The expression is parsed, never run as Python code.
### Show Text
Shows what arrives on a wire, in a panel you can select from and resize: text, a number, or any other value. Text is passed on unchanged, and a number comes out as text. The last text shown saves with the workflow. Very long text may be cut short on screen, but the output keeps all of it. It runs as an output node, so nothing has to be connected after it.
### Free Memory
Passes any value through and, on the way, unloads ComfyUI's cached models, runs Python's garbage collection and clears the CUDA cache where there is one. Put it between two heavy stages when the second one needs the room. It runs only when its input changes, like any cached node, and later stages may take longer while models load again.
### Workflow Note
A setup card for a shared workflow: a title, instructions in Markdown, each model's download link and folder, the node packs it needs, and your links. It checks the models and packs against the install it's opened in. **Edit** changes the card, and **Banner** turns it into a small section title. The note never runs, and its text is never shown as raw HTML.
### Callout
A note for the canvas whose text grows to fill it, so it reads without zooming in. Double-click to type; a blank line starts a new point and `**two stars**` stress a word. Type an arrow emoji such as ⬆️ and it turns into a teal arrow in the middle of the line. Click a node while you edit, then press an arrow, and the arrow is linked to that node: click it later and the canvas brings the node into view. It never runs and has no wires.
### Compact note and link tiles
**Callout Tile** shows a compact title or colored YouTube/Discord link button. **Workflow Note Tile** opens a full help note from a compact button. They reuse the note editors and save as distinct node types, so an older install reports a missing node instead of drawing an incompatible plain note. Install or update ComfyUI-AusBoss, restart ComfyUI and reload when either tile is missing. Existing ordinary notes and callouts keep their original types.
### Run Timer
A stopwatch for the whole run, with no wires. It starts when the queue starts running, holds the total when the run ends, and saves the last time with the workflow. Recent times are in its right-click menu. You can resize it. It only shows time and doesn't change how the graph runs.
### Step Preview
Shows the picture while it is being made, one step at a time, so you can stop a run that is going wrong after the first few steps. It has no wires: put it anywhere and it shows whichever sampler is working. **STOP** stops this run; **STOP ALL** also empties the queue. When the picture is saved, the finished picture replaces the last step. It turns on ComfyUI's step pictures for its own runs, unless you chose a preview method yourself. The [help page](js/docs/AUSBOSS_NODES_StepPreview.md) covers sharper step pictures.
### Workflow Switches
Turns parts of a workflow off and on from one small card. Select the nodes of one part, such as a box and its Save Image, press **+ Switch**, and they get one **off | on** switch with a name you choose. To pick between settings instead, set a node and press **+ Choice** for each way: the card becomes one pill. Every group can have a switch of its own too. Off bypasses the nodes, so a step in the middle of a chain can be skipped and the picture still reaches the steps after it; a setting mutes them instead. A switch can change settings instead: save what chosen nodes hold for on and for off, and one click sets Steps to 8 and turns a LoRA row on. **Done** shrinks the card to the labels and their switches. A row whose nodes are only partly on reads **mixed**. The node never runs and has no wires. An AI agent can set one up for you: point it at the [agent guide](js/docs/agents/workflow_switches.md).
## Example workflows
These are the workflows where the nodes do something core ComfyUI doesn't do by itself: outpainting and inpainting that put your original picture back, lining an edit back up with its source, upscaling a picture in tiles, masking a thing by its name or with a click, and removing a watermark from a clip. Open a JSON file from [`example_workflows/`](example_workflows), or find them under this pack in ComfyUI's template browser. Each one has a thumbnail, numbered groups and a Workflow Note with the setup steps and model downloads.

The examples use this layout: named groups for your input, the models and the result, with the plumbing inside two boxes you can double-click to open.
The workflows open with their image and video loaders empty, so you load your own picture or clip. To try one with a sample first, copy the pier photo or the short pier clip from [`example_workflows/inputs/`](example_workflows/inputs) into `ComfyUI/input/`. If you keep models in subfolders, pick your copy in each loader before you run. A green model check on the note only means the file exists; it doesn't mean the loader has that file selected. The models' licenses and any download terms are their authors'.
| Workflow | What it does | What you need |
|---|---|---|
| [Krea 2 Outpaint](example_workflows/Krea%202%20Outpaint%20%28AusBoss%29.json) | Pick a ratio or drag the orange handles out, and Krea 2 paints the new area | Krea 2 Turbo and the AnyPaint LoRA; its text encoder also writes the caption |
| [Krea 2 Inpaint Masked](example_workflows/Krea%202%20Inpaint%20Masked%20%28AusBoss%29.json) | Paint over part of a picture, say what goes there, and Krea 2 repaints only that | Krea 2 Turbo and the AnyPaint LoRA |
| [Krea 2 Rotate + Outpaint](example_workflows/Krea%202%20Rotate%20%2B%20Outpaint%20%28AusBoss%29.json) | Turn, crop and pad a picture, and Krea 2 paints the new area | Krea 2 Turbo and the AnyPaint LoRA |
| [Klein 9B Outpaint](example_workflows/Klein%209B%20Outpaint%20%28AusBoss%29.json) | Extend a picture with the PixaOutpaint LoRA, then stitch the original back | Distilled Klein 9B, its Qwen encoder and the Flux 2 VAE, the PixaOutpaint LoRA, and Qwen3-VL 8B INT8 for the caption |
| [LTX 2.3 Video Outpaint](example_workflows/LTX%202.3%20Video%20Outpaint%20%28AusBoss%29.json) | Widen a vertical clip, keeping its audio and original pixels | LTX 2.3, the distilled LoRA and the outpaint IC-LoRA |
| [Qwen Image 2.1 Edit + Realign](example_workflows/Qwen%20Image%202.1%20Edit%20%2B%20Realign%20%28AusBoss%29.json) | Type a short edit, and Realign to Source lines Qwen's result back up with your picture | Qwen Image 2.1 INT8, Qwen3-VL 8B INT8 and the 2.1 VAE |
| [Qwen Image 2.1 Upscale + Restore](example_workflows/Qwen%20Image%202.1%20Upscale%20%2B%20Restore%20%28AusBoss%29.json) | Load a small or worn photo, set the megapixels, and Qwen redraws it sharp, in tiles above about 2 | Qwen Image 2.1 INT8, Qwen3-VL 8B INT8, the 2.1 VAE, the Restore LoRA and, for Fast, the Viggle Turbo LoRA |
| [Qwen Image 2.1 Inpaint](example_workflows/Qwen%20Image%202.1%20Inpaint%20%28AusBoss%29.json) | Click a thing in your picture, say what to change, and Qwen repaints only that part | Qwen Image 2.1 INT8, Qwen3-VL 8B INT8, the 2.1 VAE, ComfyUI's own SAM 3.1 and, for Fast, the Viggle Turbo LoRA |
| [Mask by Name](example_workflows/Mask%20by%20Name%20%28AusBoss%29.json) | Type what to find in a picture, and get its mask and a cut-out with a see-through background | ComfyUI's own SAM 3.1 (ComfyUI 0.20 or newer) |
| [Mask by Touch](example_workflows/Mask%20by%20Touch%20%28AusBoss%29.json) | Click a thing in a picture, or its whole subject, and get its mask and a cut-out with hair-fine edges | ComfyUI's own SAM 3.1 and its background-removal model (ComfyUI 0.21 or newer) |
| [Simple Video Watermark Remover](example_workflows/Simple%20Video%20Watermark%20Remover%20%28AusBoss%29.json) | Find an overlay in a clip and remove it, with a one-frame comparison branch | ComfyUI's own SAM 3.1 (ComfyUI 0.20 or newer), plus `big-lama.pt` |
Every example uses only core nodes and this pack. Speed and memory use depend on the models, the sizes, the frame count and whatever else is using the GPU.
## For workflow creators
If you share workflows, a few of these nodes help the people who download them:
- **Workflow Note** puts a setup card on the canvas with each model's download link, folder and size, the node packs it needs and your links, and checks them against the reader's install.
- **Callout** is a note with big readable text and teal arrows you type with an emoji.
- **Image Compare A/B** shows the result against its source with a slider.
- **Seed** keeps runs repeatable, and **Use last run** brings back the seed the last run used.
- **Run Timer** shows how long the whole run took, so a speed note is measured.
- **Save Image** embeds the workflow in PNG, lossless WebP or JPEG XL files, with names you control.
- **Workflow Switches** gives each optional part of your workflow one off | on switch, so people skip what they don't need without breaking the chain.
- **Free Memory** between two heavy stages, and **Free VRAM** on Mask by Name and Mask by Touch, give the next model its room on a smaller graphics card.
People who download your workflow get the nodes through ComfyUI-Manager's missing-node install, like any other pack.
## Pack-wide tools
- **Help cards:** the **?** in a classic node's title bar opens its description, inputs and outputs. Nodes 2.0 and titleless nodes such as Run Timer have **About this node** in their right-click menu.
- **Recreate node 🆎** (right-click an AusBoss node) rebuilds a node saved by an older version from the current definition. It keeps the values, links, position and colors.
- **Replace with AusBoss nodes 🆎** (canvas right-click or the command palette) finds third-party nodes this pack can stand in for, such as VideoHelperSuite's Load Video and Video Combine or KJNodes' Color Match. It previews each swap and replaces the ones you leave ticked.
- **Snap into line:** a node, group or selection you drag lines up with the nodes and groups around it, edge to edge or middle to middle, and a teal line shows what it lines up with. Pulling a corner to resize lines up the same way. Hold Shift while you drag to skip it, or turn it off in Settings → 🆎 AusBoss → Canvas.
- **Line up:** select two or more nodes or groups and the selection toolbar gets a Line up button: line up edges or middles, match widths or heights, or space them evenly. With two or more nodes selected, pulling the edge or corner of one resizes them all: a column gets one width, a row one height, a grid both (turn it off in Settings → 🆎 AusBoss → Canvas → Resize together).
- **Node colors:** Settings → 🆎 AusBoss → Appearance sets the scheme for every AusBoss node (AusBoss, Graphite, Slate, Teal, Moss, Plum, Rust, Navy, Custom or Theme default). A node's right-click **AusBoss color** menu recolors just that node, and nodes you colored by hand keep their colors.
- **Run status:** under Settings → 🆎 AusBoss → Chrome, the queue status can show in the browser tab's title and icon, running nodes get live progress badges, and each node's run time can be shown.
- **Completion sound:** Settings → 🆎 AusBoss → Notifications plays a short chime when the queue finishes.
## Editor controls and settings
- Drag a number box to scrub it, or click it to type. Hold Shift for fine steps.
- Hold Shift while you turn the green rotation handle to snap to 15° steps.
- In the full-screen editor, the mouse wheel zooms, and the middle mouse button or Alt-drag pans. On the node itself, the wheel and middle mouse button still move the graph.
- **Alt+E** opens the selected node's transform editor. Change the key in Settings → Keybindings.
- The video editors have frame stepping, playback and exact timeline seeking, on the node face as well as in the editor. The Clip node's IN and OUT handles sit on the source's own frames, so the frame shown for IN is the first one decoded and the frame shown for OUT is the last.
- Your own aspect presets go in `ausboss_presets.json` beside the pack. Copy [`ausboss_presets_example.json`](ausboss_presets_example.json) to start. Your file survives updates.
LoRA Loader and Latent Size have their own gear menus for per-node preferences.
The video transform nodes read a video you pick from a list of ComfyUI's input folder (Uploads) or output folder (Outputs), never one named by a whole path, and Save Image writes only inside the output folder. No widget can point the pack anywhere else on the disk. See [the video transform help](js/docs/AUSBOSS_NODES_VideoCropRotatePad.md).
## Optional extras
Three features need an extra Python package. Install it with the Python that runs ComfyUI. For the Windows portable build, that is `python_embeded\python.exe -m pip install ...` from the portable folder.
After updating frontend files, hard-refresh the browser with `Ctrl+Shift+R`.
The default mask settings, ordinary stitching, PNG and lossless WebP don't need them. Optical flow needs RAFT weights on disk, and the node's help page explains the setup. Which video codecs you have depends on your ComfyUI environment.
## Quick start
## Compatibility and development
1. Add either transform node.
2. Pick or upload a source. For very large videos, switch the video node to `local path` mode.
Queued workflows always read local paths, but the editor's live preview of paths outside
ComfyUI's own folders is opt-in: start ComfyUI with `AUSBOSS_TRANSFORM_LOCAL_PREVIEW=1`
to enable it. This keeps the preview routes from exposing arbitrary files to anything
that can reach your ComfyUI server (for example on `--listen`).
3. Click **Open editor**.
4. Drag cyan crop handles, orange padding handles, or the green rotation handle.
5. Close the editor and connect `image` and `mask` downstream.
The pack needs ComfyUI **0.27.1** or newer. An example for a newer model needs a ComfyUI core that supports that model, and updating only this pack can't add it. Every night it is checked against the newest ComfyUI release and ComfyUI's development version. It works on the classic canvas and in Nodes 2.0, and running a workflow doesn't need an editor open.
Use the matching examples in [`example_workflows`](example_workflows) as small starting graphs.
## Editor controls
- Eight crop handles with generous hit targets
- Four visually distinct padding handles
- Rotation handle with alignment grid and `Shift` snapping to 15 degrees
- Free or ratio-locked crop
- Mouse-wheel zoom; middle-mouse or `Alt`-drag pan
- Reset view, reset padding, and reset all
- Video first/last, ±1, ±25, ±50, ±100, playback, and timeline scrubbing
## Settings
Under **Settings → 🆎 AusBoss** you can pick an **AusBoss node color** scheme
(Graphite, Slate, Teal, Moss, Plum, Rust, Navy, or **Custom** with your own
picked tint). It recolors every AusBoss
node in the open workflow immediately and applies to nodes you add later.
Nodes you have colored by hand keep their own colors, and **Theme default**
returns everything to the stock look. Right-click any AusBoss node for a
per-node **AusBoss color** override, and press **Alt+E** (rebindable in
Settings → Keybindings) to open the selected transform node's editor.
Custom crop aspect-ratio presets live in an optional `ausboss_presets.json`
next to the pack — copy `ausboss_presets_example.json` to start; your file is
gitignored and survives updates.
Under **Chrome**: the browser tab's icon and title can show rendering state
and queue depth (on by default), **live status badges** report progress on the
node itself while it works — `frame 43/120` through a video inpaint — (on by
default), and optional **node runtime badges** stamp each node with its seconds
after a run (off by default).
## Compatibility
- Classic V1 node definitions for broad ComfyUI compatibility
- Classic canvas and Nodes 2.0 frontend support
- API-mode execution does not require the editor to be open
- Transform editors validated on ComfyUI `0.27.1`; the pack scaffold was also validated on `0.28.0`
The declared minimum is ComfyUI `0.27.1` (`supported_comfyui_version` in `pyproject.toml`, which ComfyUI-Manager enforces at install time). If a frontend update changes custom-widget behavior, please include your ComfyUI and frontend versions in the issue.
## Development
Backend changes need a full ComfyUI restart, and frontend changes need a hard refresh. Run the backend tests with ComfyUI's Python:
See [`AGENTS.md`](AGENTS.md) for compatibility rules and [`docs/adding_a_node.md`](docs/adding_a_node.md) for the node checklist.
The backend tests run in separate processes, because their offline ComfyUI stubs must not leak between files. [CONTRIBUTING.md](CONTRIBUTING.md) covers bug reports and pull requests. [`docs/live_testing.md`](docs/live_testing.md) covers canvas, mouse-drag, save and reload, and workflow checks, and [`docs/adding_a_node.md`](docs/adding_a_node.md) lists what a new public node needs.
## Feedback
Bug reports and workflow ideas are welcome in [issues](https://github.com/ausboss/ComfyUI-AusBoss/issues). Security problems go through [SECURITY.md](SECURITY.md). Every release is in the [changelog](CHANGELOG.md). If a node saves you time, a star on GitHub helps other people find it. There is also a [Ko-fi page](https://ko-fi.com/ausboss) if you want to leave a tip.
@@ -20,22 +20,26 @@ Every visible field and output must earn its place. Published IDs, input order,
1. Put independently testable processing in an underscore-prefixed helper under `nodes/`.
2. Add one V1 wrapper in `nodes/node_<purpose>.py` with `INPUT_TYPES`, `RETURN_TYPES`, `FUNCTION`, `CATEGORY`, and both mapping dictionaries — keys as string literals, never a `NODE_ID` variable, or registry scanners cannot see the node.
3. Add the module to `NODE_MODULES` in `__init__.py`.
4. Add frontend JavaScript only when the normal schema cannot provide the required interaction.
3. Add the module to `NODE_MODULES` in `__init__.py` and the key to `PUBLIC_NODE_IDS` in `scripts/validate_nodes.py`; the validator fails on a registered key it does not know. A key kept only so old workflows load goes in `LEGACY_NODE_IDS` instead.
4. Give the node its face. Any INT, FLOAT, BOOLEAN, COMBO or STRING widget on a public node is shown through a widget card entry in `js/widget_cards/index.js` (see CONTRIBUTING.md, Conventions: numbers scrub, booleans are off | on pills, one linkable widget per row, `top: true` or `forceInput` for values people wire). Write more frontend JavaScript only when the card grammar cannot provide the interaction (a stage, a player, an editor).
5. Namespace every route, extension, event, DOM marker, CSS class, cache, and browser state with `ausboss`.
6. Chain lifecycle hooks through `chainCallback`; never replace core or third-party prototypes directly.
7. Keep normal workflow and API execution independent from the custom frontend.
8. Ship user-facing toggles as ComfyUI settings under the `AusBoss.*` namespace (see `.claude/skills/ausboss-node-brand/SKILL.md`). Node coloring is automatic for `AUSBOSS_NODES_*` classes via `js/appearance/` — no per-node work.
8. Ship user-facing toggles as ComfyUI settings under the `AusBoss.*` namespace. Node coloring is automatic for `AUSBOSS_NODES_*` classes via `js/appearance/` — no per-node work.
9. A `VALIDATE_INPUTS` names only the inputs it reads, never `**kwargs`. ComfyUI skips its own range and list checks for every input a validator names (naming `input_types` also switches off its link checks), and repeats a failed check once per named input. Checks that need several values can run in the node itself. `tests/test_validate_inputs.py` pins what each validator names.
## Document and prove
- Add rich help at `js/docs/<exact-mapping-key>.md`.
- Add a minimal workflow and matching JPEG under `example_workflows/`.
- Add rich help at `js/docs/<exact-mapping-key>.md` (the validator checks it exists) and a section in `README.md`. Name card controls the way the node face shows them, with the widget name alongside: **Tone match** (`color_match`).
- Show the node in an example under `example_workflows/`, a new one or an existing one it fits: a Workflow Note, numbered groups holding every node, no overlaps, and a matching `.jpg` of a real output. CONTRIBUTING.md, Exampleworkflows, lists the rest.
- A DOM panel goes into `tests/panel_guards.test.mjs` as `mustGrow` or `fixedByDesign`.
- At the next release, name the node in the pyproject `description`: Registry and Manager search read it.
- Extend `scripts/validate_nodes.py` when the new contract needs a permanent assertion.
- Add the node to the API snapshot: `AUSBOSS_COMFY_ROOT=<ComfyUI> python tests/test_node_api.py --update` with ComfyUI's Python. From then on its inputs and outputs may only be appended.
- Add pure Python and dependency-free JavaScript tests.
- Compile with ComfyUI's embedded Python.
- Verify `/object_info/<mapping-key>`, ownership, served assets, routes, API execution, and queued dimensions/masks.
- Test the actual canvas in Classic and Nodes 2.0, including save/reload, duplication, graph zoom, source replacement, and teardown.
- Scan the diff for paths, hosts, secrets, obsolete branding, placeholders, agent attribution, and non-ASCII import output.
- Test the actual canvas in Classic and Nodes 2.0, including save/reload, duplication, graph zoom, source replacement, and teardown. Drag a real link onto every card row and socket and look at where it lands (`docs/live_testing.md`).
- Scan the diff for paths, hosts, secrets, placeholders and non-ASCII import output.
Do not release a node that only works in the editor, only works for one source, or returns debugging outputs users do not need.
- **`scripts/dev/`** — small Node scripts that talk CDP directly, with no
package dependencies. Use a Node version with built-in `fetch` and `WebSocket`:
| script | does |
|---|---|
| `cdp.mjs <port> <workflow.json\|-> <out.png\|-> [eval.js] [waitMs]` | opens/reuses the Comfy tab, optionally loads a workflow, evaluates a JS file, screenshots (a returned `__clip` rect clips the shot), prints the eval result as JSON |
| `cdp_drag.mjs <port> <setup.js> <mid.png\|-> <after.png\|-> [check.js]` | `setup.js` returns `{from, to, __clip}` in client pixels; presses at `from`, moves to `to`, screenshots mid-drag, releases, evaluates `check.js`, screenshots again |
| `audit_workflows.mjs <port> <output-dir> [workflow.json ...]` | loads every example (or selected files), exports API and loaded graphs, captures screenshots, and fails on overlaps, missing nodes, browser exceptions, or exposed AusBoss number widgets |
| `wheel_sweep.mjs <port> [--vue] [--only=Type,Type] [--workflow=file.json] [--shots=dir]` | turns the mouse wheel at every point of every node of the pack (or of the nodes in one workflow) and fails on any spot where the graph does not zoom, naming the element that took the wheel; `--real` sends real wheel turns, slower and on a coarser grid |
| `face_smoke.mjs <port> [--vue] [--only=Type,Type]` | puts one of each node of the pack on an empty graph and fails when a face does not come up: no node, a panel that is not on the page or has no size, an error thrown, a file of the pack that did not load. Needs no model, so it also runs in the nightly ComfyUI check |
| `cdp_nav_debug.mjs <port>` | navigates the tab in place and logs every CDP event, for when a tab stops answering |
| `cdp_hang.mjs <port>` | pre-arms the debugger on a fresh tab, loads ComfyUI, and pauses to print the stack if the page stops answering |
`CDP_RELOAD=1` closes the Comfy tab and opens a fresh one with the cache
cleared — required after editing any `.mjs`, which the frontend caches
hard.
## Screenshot convention
An eval creates the node at `pos [0, 0]`, sets `app.canvas.ds.scale = 1` and
A note for the canvas. Type a line or two and the text grows to fill the
note, so people can read it without zooming in. Type an arrow emoji such as ⬆️
to point at the part you are talking about.
Use it to explain a step in a workflow you share. It never runs and has no
wires.
## Tile presets
Right-click the Callout and choose **Title tile** to give your existing text a black heading with a teal accent. **Set YouTube link tile** and **Set Discord link tile** fill in the AusBoss link and label. Click the label to open the link, or drag the tile to move it. Choose **Plain callout** to return to the standard style with the current text.
- Press **Edit** on a tile to open a larger editor. A link tile has separate **Button text** and **Link address** fields. Existing mixed text stays in a text box.
- Double-click a title to edit it. **Save** or `Ctrl+Enter` keeps the change; **Cancel** or `Esc` leaves the original text.
- Drag the visible bottom-right grip to resize. You can also focus the grip and use arrow keys; hold **Shift** for smaller changes. Resizing keeps the tile in place and supports Undo.
## Writing
- On a plain callout, double-click the note, or press **Edit**, to type. Click outside the box to
keep the text. **Esc** drops what you typed.
- A blank line starts a new point. Each point gets a thin teal line beside it.
- Put two stars around a word to stress it: `**Resize is on.**`
- For the details, make a line **small print**: put the caret on it and press
**Small** under the text box. It is drawn small under the line above, so
the first line stays big enough to read from far out. Press **Small** again
to make it normal.
- A line that starts with `##` is read the same as one that doesn't, so notes
written for the core Markdown Note paste in as they are.
- Drag the corner to change the size. The text grows or shrinks to fit.
## Arrows
Type an arrow and it turns into a teal arrow, in the middle of your line.
- Use an arrow emoji: ⬆️ ⬇️ ⬅️ ➡️ ↗️ ↘️ ↙️ ↖️. The pointing hands 👆 👇 👈 👉
and the plain arrows ← ↑ → ↓ work too, and so do `->` and `<-`.
- Or press an arrow in the row under the text box while you edit.
- Put the note next to the part it explains and point the arrow at it.
### Link an arrow to a node
While you edit, click a node on the canvas, then press an arrow. The arrow is
linked to that node. Click a linked arrow later and the canvas brings the node
into view, the same way the frame button in Workflow Switches does. Hover the
arrow to see which node it goes to.
In the text a linked arrow is written like `➡️#12`, where 12 is the node's
number. You can leave the `#12` out to get a plain arrow. If the node is
deleted the arrow stays, faded, and does nothing.
## Links
A note can link to a page on the web, or to a part of the workflow.
- **To a page:** select the words, press **Link** under the text box, and
paste the address. Or select the words and paste the address straight over
them. Click the words later and the page opens in a new tab. A small ↗
after the words shows that they leave ComfyUI, and hovering shows where
they go.
- **To a node or a group:** while you edit, click the node or the group on
the canvas, select the words and press **Link**. Click the words later and
the canvas brings it into view. An arrow can point at a group too: click
the group, then press an arrow.
- A note that is nothing but one link is a big button for it: its words fill
the note.
In the text a link is written `[the words](https://example.com)`, or
`[the words](#12)` for node 12 and `[the words](#g3)` for group 3.
## Technical details
- The text is stored in the node's one hidden `text` input, so saving,
undo and copy work like any other node. Nothing else is saved with it.
- A note copied together with the nodes and groups its links name links to
the copies. A link to anything left behind keeps its number.
- In the text, small print is a line that starts with six `#` and a space:
`###### Off, your words go in as typed.` Small print is 62% of the note's
text size and never under 11 px.
- Notes written before small print look the same. An older version of the
pack drops those six `#` like any other heading mark, so a note with small
print still reads right there, with every line at one size.
- The text size is the largest whole pixel size, from 12 to 30, that fits the
note. A very long text at the smallest size is cut off at the bottom: make
the note taller.
- Only `http` and `https` addresses become links. Anything else in the round
brackets leaves the text as typed. A link opens in a new tab and is never
opened by the note itself: only your click does.
- A link from someone else's workflow goes where its author pointed it. Hover
it to read the address before you click.
- An older version of the pack shows a link as typed, brackets and address
included, so the address can still be read and copied there.
- The font is IBM Plex Sans when the computer has it, otherwise the system
font. The node loads no fonts and makes no network requests.
A compact title or colored link button for a workflow. Choose **Title tile**, **Set YouTube link tile**, or **Set Discord link tile** from its menu. Press **Edit** to change its text or link; you can also double-click a title. Drag its visible corner to resize.
It uses the same text, arrows and links as [Callout](AUSBOSS_NODES_Callout.md). **Plain callout** returns to the ordinary note face. Nothing runs and it has no outputs.
Tiles save with their own node type. If ComfyUI reports this node missing, install or update **ComfyUI-AusBoss**, restart ComfyUI, and reload the workflow. An older pack cannot draw the tile correctly. Keep the missing node so its text and settings are preserved.
Loads an image and applies one reusable **rotate → crop → pad** transform. Click **Open editor** for the full-screen canvas; normal queued and API execution use the saved widget values without needing the editor.
Loads a picture and lets you turn it, crop it and add empty space around it, by
dragging handles on the node. Use it to frame a picture for a model, or to add
space around a picture that the model then fills in (outpainting).
## Controls
The picture is turned first, then cropped, then padded. The node also makes the
**mask** that tells the model where to paint, and a **stitcher** that puts your
original back over the result.
- **image**: Image picker/upload from ComfyUI's input folder.
- **rotation_degrees**: Clockwise rotation before crop and padding.
- **crop_aspect_ratio**: Free crop, source ratio, or a fixed ratio.
- **crop_x / crop_y / crop_width / crop_height**: Crop in rotated-image pixels. Width and height `0` mean the full available dimension.
- **pad_left / pad_top / pad_right / pad_bottom**: New pixels around the crop.
- **feather**: Feathers the mask into kept pixels and fades the image edge into the fill color, so outpaints blend instead of ending at a hard seam.
- **canvas_multiple**: Rounds the final canvas up by adding the minimum extra pixels to the right and bottom.
- **fill_color**: `#RRGGBB` or three RGB values used for generated pixels.
## Quick start
1. Choose a file above the preview, or click **Load**. Dropping a
picture onto the node works too.
2. Drag the handles on the picture. Cyan squares crop. Orange diamonds add space
on one side. Orange corners make the whole canvas bigger or smaller. The
green knob turns the picture.
3. Or tap a ratio such as **16:9** to pad the picture to that shape.
4. Wire **image** and **mask** to your model, and **stitcher** to Stitch Inpaint
after the sampler.
**Open editor** shows the same handles on a full-screen canvas, with zoom and
pan. **Save & close** keeps your edits. **Cancel** or Escape puts everything
back, and asks first if anything changed. You do not need the editor: a run
uses what the node shows.
## On the node
From top to bottom:
- **File picker**: choose **Inputs** for uploaded pictures or **Outputs** for renders, newest first. The list shows the
picture under the pointer and filters as you type. **Load** adds one. A
picture ComfyUI made, named like `photo.png [output]`, loads too, as it does
in core Load Image. The gear holds the node's display options.
- **The picture**, with its handles.
- **The size line**: each step that sets the output size, ending with the size
the run gives you.
- **Crop, pad & resize**: a collapsible group (closed to start, with the fill
swatch and a short summary on its bar) holding:
- **Aspect ratio** and **Fit to ratio** (Crop or Pad), above the orientation button, the ratios (1:1, 4:3, 3:2, 16:9,
21:9) and the padlock.
- **Align** and the two **Center** buttons.
- **Fill** and **Feather**.
- **Resize**, with **Megapixels** and **Step** alongside it when Resize is on.
- **Open editor**, **Reset crop** and **Reset**.
Crop and padding have no number boxes. You set them by dragging, and the size
line shows the result. Picking another picture starts the framing over
(rotation, crop and padding); Fill, Feather, Align, Resize and a lit
ratio stay.
### Ratios, Fit and the padlock
Tap a ratio under the picture to pad the picture to it: every pixel stays and
fill bands are added around it, centred. Set **Fit** to crop and the ratio trims
the picture instead. Tap the lit ratio again to go back to the whole picture.
You can set **Fit to ratio** before picking a ratio. If a ratio is already lit, switching between Crop and Pad applies it immediately.
- **A lit ratio is the shape the canvas has now.** Drag a handle to another
shape and the ratio goes dark; the row says **Custom** and the size line under
the picture gives the real ratio (`1.49:1`).
- **A picture that already has the shape gets nothing added.** Within about 1%
counts, so a nearly 9:16 photo gets no 1 px band. The size line under the
picture says so ("already 9:16: pick another ratio or turn it"), and while a
ratio is lit it names it where it acted (`pad to 16:9 2532×1424`). A lit ratio
saved in a workflow is used again on every new picture you load.
- **The padlock** at the end of the row keeps the shape while you drag: pull one
side out and the other side's padding follows, split evenly. The row says
**Held** while it is on. On the untouched picture (the row says **Source**)
there is no shape to hold, so it stays off. **Reset** and tapping the lit ratio
turn it off, and **Reset crop** also takes away the bands it added.
- **The orientation button** at the start of the row turns the shape on its side:
16:9 becomes 9:16, padded around the picture (crop mode turns the crop box
about its centre). The picture itself never rotates. With nothing picked it
only turns the ratios, so the next tap goes that way.
- **Drag the picture itself** to move it inside its padding; the canvas keeps its
size. Over the picture, the cursor and small arrows show which ways it can go.
- Rotating keeps a lit ratio: the padding follows the turned picture.
### Corner handles and Center
The four orange corners outside the picture make the whole canvas bigger or
smaller and keep its shape: drag one out to add room on two sides at once, for
example to zoom out before an outpaint. The corner across from the one you drag
stays where it is. Hold **Alt** (Option on a Mac) while you drag to change all four
sides at once, so the picture keeps its place in the middle. You can press or let
go of Alt during the drag. A corner stops when one of its two sides has no padding
left to take away.
**Center** moves the crop to the middle of the rotated source, side to side or
top to bottom. If there is padding on that axis, the same click also balances
the bands around the crop. The crop and output keep their sizes, and the
generated mask follows the new placement. Each button dims when its axis is
as centered as the pixels allow.
The orange diamonds move one side at a time, and the padlock works on them as
described above. The corners keep the shape with or without the padlock. The
green rotate knob moves aside when a corner handle sits where it usually goes.
### Fill, Feather and Resize
- **Fill** (`fill_color`): the colour of the added space and of the empty
corners a turn leaves. It starts gray. Added space on the picture is drawn in
the real fill colour with a faint hatch, and only the part the crop cuts away
is darkened.
- **Feather** (`feather`, 24 px to start): softens the edge of the **mask** into
the kept picture, so the model and the stitch blend the seam. The image itself
keeps a hard edge against the fill. That solid band is what outpaint models
and LoRAs recognise as the area to paint.
- **Resize** (`resize_to_megapixels`, off to start): resizes the finished output
to a size budget and keeps its shape. Turning it on opens a row with
**Megapixels** (`megapixels`, the budget) and **Step** (`resolution_steps`,
the number each resized side rounds to; 8 or 64 suits most image models).
**Method** (`resize_method`) is in the editor, under **Resize output**.
The edge between your picture and the fill stays one clean line at any size.
### Align
**Align** (`canvas_multiple`) makes the width and height divide evenly by
the number you pick. Some models need sizes divisible by 8, 16 or 32. At 1 it is
off. Its arrows go 1, 8, 16, 24 and so on; hold Shift to step by 1.
Where you padded, the padding grows by the few pixels needed: on the right or
bottom band if there is one, otherwise on the left or top. Where you did not
pad, no fill is added. The picture is trimmed by a few pixels instead, evenly
from both edges, so an edge you did not pad never gets a thin strip for the
model to paint. A picture smaller than the number itself is padded on the right
or bottom.
### The size line
The line under the picture names every step that sets the output size, in the
order the run applies them, with the size the run emits last and brightest:
`crop 2080×1170 → pad 2208×1298 → round to 64 2240×1344 → resize 1344×768`. A step
that changes nothing is left out, and "round to" is **Align** at work. An
amber line warns when rounding each side to the Step stretches the picture by
more than 1% (`Resize makes the picture 1.5% taller`); its tooltip names a
Step that avoids it, and with Fit on pad, **Pad to keep shape** adds a few pixels of
padding so nothing stretches. It also warns when the resize undoes **Align**. Hover the line for the same breakdown line by line; the editor's right
panel shows it too.
The amber line keeps still while you drag a handle or a number. If the drag
makes it untrue it goes away, and the line for the new size comes back a
moment after you let go. The space for the warning stays under the dimensions. On a narrow node, hover
over shortened text to read it in full.
### Reset crop and Reset
**Reset crop** shows the whole picture again without changing rotation or
padding. **Reset** clears rotation, crop and padding and turns the padlock off;
Fill, Feather and **Align** stay.
## Drawing a mask
Right-click the node and choose **Open in MaskEditor** to paint over the parts
you want the model to repaint. After **Save**, the picture on the node shows
them in teal, and the teal moves with the picture when you crop, turn or pad
it. The full editor shows it too. The run treats painted parts like the
padding: it fills them with the fill colour and marks them in the **mask**,
so under the teal you see the fill colour, not your picture.
**Keeping your picture under the paint.** Some workflows need what was under
the paint: an inpaint with denoise under 1, or an edit model that has to see
what it changes. Open the editor and set **Painted area** to **keep**, in the
**Inpaint & Stitch** section. The **mask** still marks the painted parts, and
your picture stays under them in **image**, **original** and
**prompt_image**. The picture on the node still shows the fill colour under
the teal.
See-through parts of a PNG show in teal the same way, because the node paints
them too. To hide the teal, click the gear next to **Load** and turn off **Show the mask on the picture**. The choice applies
to every Image Crop + Rotate + Pad in this browser.
- **image**: the turned, cropped and padded picture. Every frame of an animated
image gets the same framing.
- **mask**: White where the model paints: the padding, the empty corners a turn leaves, and the see-through parts of your picture.
- **stitcher**: Wire to Stitch Inpaint to restore the kept canvas around an outpaint result, blended into the source by **Blend** (32 px unless changed). It follows the final resized canvas. After a hand crop, set **Paste onto** to original in Stitch Inpaint's gear menu to get your whole picture back with the edit in place.
- **original**: your picture before rotation, crop, padding or resize, with
see-through parts shown as white (your picture there when **Painted area**
is on keep).
- **width** / **height**: The output size after the transform and any resize.
- **prompt_image**: The image again, with see-through parts shown as white instead of the fill colour. Wire it to whatever writes your prompt. It is the same as **image** for a picture with no see-through parts, and when **Painted area** is on keep.
## Editor gestures
## See-through pictures
Drag cyan squares to resize the crop, drag inside to move it, orange diamonds to add padding, and the green handle to rotate. Hold `Shift` while rotating to snap to 15 degrees. Use the wheel to zoom and middle mouse or `Alt`-drag to pan. The same handles work directly on the node's compact preview (fit-only there — the wheel keeps zooming the graph); zoom and pan are editor-only.
A PNG can have see-through parts: a product cutout with no background, a
photo with round corners, a picture with a hole in it. The node treats those
parts like the padding. They are filled with the fill colour, marked in the
mask, and the model paints them. Stitch Inpaint never puts them back over
the painted result.
The node performs no network requests and writes no files beyond a normal user-initiated ComfyUI upload.
Use **prompt_image** for the node that writes your prompt. There the
see-through parts show as white, the way a picture viewer shows them, so the
prompt describes a real backdrop. Shown the gray fill instead, a prompt
writer calls it a "gray backdrop" and the model keeps the flat gray. The
model itself still gets **image**, with the fill colour it was trained on.
A picture with no see-through parts comes out exactly as before.
### Technical details
- A pixel counts as see-through when it is less than 90% as solid (alpha)
as the most solid pixel in the picture. Measuring against the most solid
pixel keeps a picture saved at, say, 50% opacity throughout whole instead
of painting it over.
- A picture that is at least 90% solid everywhere is left exactly as it was
loaded. Generated pictures often carry alpha values of 249-254 in places,
and those pictures do not change.
- Kept pixels are used fully solid, in their own stored colour. See-through
pixels never reach the canvas: their stored colour (often black, or the
old background) is what used to leave a dark or light ring round a cutout.
- Why 90%: on an oval photo whose edge stores colours darkened by their
alpha, a 50% cut kept a rim up to half dark, the ring the model painted
back. At 90% the kept rim is at most 10% darker, and matting noise inside
solid subjects stays above it, so no holes open there.
- A turned picture's see-through parts turn with it, like its corners. In
**prompt_image** they show white while the padding and the corners keep
the fill colour.
- The MaskEditor saves its mask as see-through parts of the picture, so the
same rule applies: a stroke more than 10% strong is painted in full,
including one at the MaskEditor's default 70% opacity.
## The editor
**Open editor** opens the full-screen canvas. Its left side holds the node's own
controls: the same ratio row with its orientation button and padlock, the same
**Fit** switch, and the same number boxes (drag to scrub, click to type, Shift
for fine steps). The editor adds a few things the node does not show:
- **Rotate → Degrees** (`rotation_degrees`): type an exact turn, clockwise.
Each step is 1°, or 0.1° with Shift. **Reset rotation** sets it back to 0.
- **Padding & mask → Reset padding** removes all padding. The same section has
Fill, Feather, Align and a **Center** row.
- **Resize output → Method** (`resize_method`): how the resize samples the
picture. `lanczos`, the default, is the sharp one.
- **More**: ratios you added in `ausboss_presets.json` that no button shows.
The list only appears when you have some.
- **Reset view** undoes zoom and pan. **Reset transform**
clears rotation, crop, padding, fill, feather and Align; the picture,
resize and stitch settings stay.
- **Preview**: a small picture of the finished output while you work.
A ratio replaces the existing crop and padding and keeps rotation, fill and
resize settings. **Align** and the resize **Step** can slightly change
the fitted shape. The size box on the stage sits clear of the handles.
### Handles and gestures
Drag cyan squares to resize the crop, drag inside to move it, orange diamonds to add padding on one side, orange corners to make the canvas bigger or smaller in its own shape (hold `Alt`, Option on a Mac, for all four sides), and the green handle to rotate. Hold `Shift` while rotating to snap to 15 degrees. The knob turns all the way round either way: past 180° it carries on from -180°, so an upside-down picture turns back up whichever way you drag. Rotating keeps the crop's size and keeps it over the same part of the picture, whichever control turns it (knob, number box, Reset rotation); with no crop the canvas grows to hold the tilted picture. The knob keeps clear of the padding handles and crop squares. Use the wheel to zoom and middle mouse or `Alt`-drag on an empty spot to pan. The same handles work directly on the node's compact preview (fit-only there — the wheel keeps zooming the graph); zoom and pan are editor-only.
### Inpaint & Stitch
The editor's right side holds the stitcher's settings, the same as on the clip node:
- **Blend** (`stitch_blend`, default 32): the ramp, in output pixels, where generated
pixels fade over the source. It is separate from **Feather**, which shapes the mask.
- **Show blend** tints the stage with the paste mask: the generated area (painted and
see-through parts included), the feather, then grow and blend applied through any resize.
Draw boxes on a canvas and say what goes in each one: a thing, or words to
write in the picture. The picture you make then has each thing where you put
it. Use it for layout prompts, such as MOSS-Image's.
It does what ComfyUI's own Create Bounding Boxes does and gives the same
outputs. Its canvas also works in the classic node look, where Create Bounding
Boxes only shows "Node 2.0 only".
## How to use it
1. Set the canvas size in **Canvas** (W × H), or wire `width` and `height` from
Latent Size, so the boxes and your picture have the same size.
2. Drag on the canvas to draw a box. Its tag shows its number and its type,
such as **1 object**.
3. Under the canvas, the row **Box 1 is** sets what the chosen box holds,
**object** or **text**. Then type its words:
- **object**: the **Description** says what is in the box, for example "a
red bicycle leaning on a brick wall".
- **text**: the **Text** is the words to write in the picture, for example
"OPEN 24 HOURS". The **Description** says how they look, for example
"pink neon letters".
4. Wire `elements` to the `element` input of MOSS-Image Layout Prompt, and give
that node the same width and height.
Wire `preview` to a Preview Image to see the boxes with their numbers and words.
## Change the words in a picture
The `mask` output is white inside every box. Use it to change only one part
of a picture, such as the words on a sign or a shirt.
1. Wire a Load Image into `background` and pick your picture. It shows on the
canvas right away.
2. Draw a box over the old words, a little wider than the old words on every
side (about 12 pixels), or bits of the old letters can show at its edge.
Keep faces and hands out of it: whatever is in the box is drawn again.
The arrow keys move the chosen box one pixel at a time.
3. If the words run at a slant, turn the box to match them: drag the round
handle over the box (see **Turning a box** below).
4. Make it a **text** box. Type the new words in **Text**, and how they look in
**Description**, for example "white letters on a red sign".
5. Soften the edge of `mask`, for example with Mask Refine 🆎 at a blur of
about 6, or a line can show around the box. Then wire it into the inpaint
part of your workflow: Set Latent Noise Mask, so only the box is drawn
again, and Image Composite Masked, so everything else stays as it was.
6. With MOSS-Image, wire `elements` to MOSS-Image Layout Prompt. The model
gets the new words from the box, and your prompt only describes the picture.
## Turning a box
Words on a sign or a shirt often run at a slant. Turn the box to lie along
them, and only the slanted words are drawn again.
1. Click the box to choose it. A round handle shows over the middle of its
top edge.
2. Drag the handle around the box. Hold Shift to turn it in steps of 5
degrees.
3. Or type the angle in **Turn**, under the canvas: plus turns the box
clockwise. **0°**, or a double-click on the round handle, makes the box
level again.
The box's tag shows how far it is turned, for example **1 text 25°**. A turned
box still moves, sizes and takes the arrow keys. Its corners may reach past
the edge of the picture, so it can cover words that run to the edge.
A turned **text** box tells the model the slant for you: its description in
`elements` gets "tilted about 25 degrees, running downhill to the right" (or
"uphill") added, so you do not have to type it.
If the picture is resized on its way to the model, resize the mask with it:
Image Resize 🆎 takes a mask and gives it the same size and crop.
## Drawing and moving boxes
| Do this | To get this |
|---|---|
| Drag on an empty spot | Draw a new box |
| Shift + drag over a box | Draw a new box on top of it |
| Drag a box | Move it |
| Drag a corner or an edge of the chosen box | Make it bigger or smaller |
| Drag the round handle over the chosen box | Turn it (Shift: steps of 5 degrees) |
| Double-click the round handle | Make the box level again |
| Double-click a box | Type its words |
| Click a box's chip under the canvas (**1 text**, **2 object**) | Choose that box, also one hidden under another. When there are more boxes than chips, **‹›** step through them |
| Delete | Remove the chosen box |
| Arrow keys | Move the chosen box one pixel (Shift + arrow: ten pixels) |
| Ctrl + D | Copy the chosen box |
| Esc | Let go of the box, or put back a box you are dragging |
| Hold Ctrl (or Alt) while dragging | Place the box freely, off the grid |
The keys work while the canvas has the focus: click the canvas, or a box's
chip, first.
The handles and the tags keep their size on your screen when you zoom the
graph in or out, and a box too small for eight handles keeps its corners.
Over a picture the boxes are see-through and show no words, so you can read
what is under them; the chosen box's words are under the canvas.
An **object** box does not write words. If you typed words while it was a
**text** box, a line under the canvas says they are kept: click **text** and
they come back.
## The order of the boxes
Box 1 is at the back, and the last box is in front, so draw the things at the
back first. The two square buttons under the canvas move the chosen box back or
forward. MOSS-Image Layout Prompt's `character_boxes` takes these numbers.
## Colors
A box can have up to five colors for the model to use on that thing. **+** adds
a color, a click on a color changes it, and a right-click (or its cross)
removes it. The first color also colors the box on the canvas.
## The bar above the canvas
- **Grid**: boxes snap to a grid while you draw, move and size them with the
mouse. It is on for an empty canvas and off over a picture on `background`,
so a box can go anywhere on the picture. Click it to turn it on or off; the
node keeps your choice. Hold Ctrl (or Alt) while dragging to leave the grid
for one drag. The cells are 32 pixels, bigger on a very big canvas. A
turned box does not snap: it moves and sizes one pixel at a time.
- **Layouts**: start from a ready arrangement: one in the middle, two side by
side, three across, top and bottom, a title over a picture, four corners.
The boxes you already have move into the new places, level, and keep their
words.
- The canvas size, and where it comes from: **picture** when a picture is on
`background`, **wired** when width and height are wired.
- **Clear** removes every box.
- **⚙** (the gear): **Grid size**, the pixels from one grid line to the next:
Auto (32 pixels, more on a very big canvas), 8, 16, 32 or 64. The node keeps
it.
## Inputs
| Input | What it does |
|---|---|
| `background` | A picture to draw on. It shows under the boxes, and the canvas takes its size and shape, also when `width` and `height` are wired: the boxes are drawn on the picture, where the `mask` is made. The Canvas row dims while a picture is wired. |
| `bboxes` | Boxes to start from: bounding boxes, prompt elements (such as the `elements` of another Layout Boxes), or either as JSON text. New boxes on this wire fill the canvas when the workflow runs. Your changes on the canvas then stay until the boxes on the wire change. After **Clear**, the next run fills the canvas from the wire again. |
| `width`, `height` | The canvas size in pixels. Wired from Latent Size, the canvas takes that size and shape at once. |
## Outputs
| Output | Use |
|---|---|
| `preview` | A picture of the boxes with their numbers, words and colors, to check the layout. Turned boxes are drawn turned. |
| `bboxes` | The boxes in pixels of the canvas (x, y, width, height), for nodes that take bounding boxes. A turned box gives the straight box around it. |
| `elements` | The boxes as prompt elements. Wire it to MOSS-Image Layout Prompt. A turned box gives the straight box around it, and a turned text box says its slant in its description. |
| `mask` | White inside every box, black elsewhere, to change only those places of a picture. A turned box is white inside its turned shape only. |
## In place of Create Bounding Boxes
Layout Boxes has the same inputs and the same outputs in the same order (and
`mask` after them), so the wires move straight over. For the same boxes it
gives the same `bboxes` and `elements`, and MOSS-Image Layout Prompt makes the
same prompt from them.
The boxes drawn on a Create Bounding Boxes do not move over by themselves. To
take them along, wire that node's `elements` into `bboxes` of Layout Boxes and
run once: they fill the canvas. Then remove the old node.
## Technical details
- The boxes are kept on the node as text, in the hidden `layout` input. Each
box's place is kept as parts of the canvas from 0 to 1, so a new canvas size
keeps every box at the same place. An API prompt can set it:
Sends a prompt — and optionally an image — to a local LM Studio server and
returns the reply as text. Anything that speaks the OpenAI-compatible chat
API works too: Ollama's compatibility mode, llama.cpp's server, a remote
box. Typical jobs: expanding a rough idea into a full prompt, captioning an
image for img2img, or rewriting text mid-workflow.
## Controls
- **prompt**: What to ask. Required unless an image is connected — an image
with an empty prompt asks for a plain description.
- **system_prompt** (optional): Role and rules, e.g. *"You write
single-paragraph Stable Diffusion prompts. No preamble, no quotes."*
Empty sends no system message.
- **endpoint**: The server's base URL — LM Studio shows it on the Developer
tab, default `http://127.0.0.1:1234/v1`. A bare `host:port` or a full
`/v1` path both work.
- **model**: Model identifier as LM Studio lists it. **Empty uses whatever
the server has loaded**, which is the everyday case — no fetched dropdown
to go stale when LM Studio was not running at ComfyUI startup.
- **temperature**: 0 is near-deterministic, 0.7 balanced. Captioning likes
0.2–0.5.
- **max_tokens**: Longest allowed reply; `-1` leaves it to the server.
Reasoning models spend tokens thinking before answering — give them room.
- **seed**: Sent to the server, and either way a changed seed re-runs the
node instead of replaying the cached reply. This is the re-roll knob.
- **timeout_seconds**: How long to wait. Cold-loading a big model or a long
think can need minutes.
- **image_max_edge**: Downscale a connected image so its longest edge fits
this before sending (`0` = full size). Vision models resize internally
anyway, and a smaller upload prefills much faster.
- **image** (optional): For vision models. Only the **first frame** of a
batch is sent — pick one with Select Frame 🆎 for video. A
text-only model will refuse an image server-side.
## Outputs
- **text**: The reply with any `<think>` blocks removed — safe to wire
straight into a text encoder.
- **thinking**: What a reasoning model thought before answering; empty for
ordinary models. Handy in a preview node while tuning prompts.
## Behavior
- Runs off the executor thread, so the UI stays responsive during a long
generation; the timeout is the backstop.
- Caches like any node: identical inputs replay the saved reply instantly.
Change the seed to force a fresh generation.
- Errors are actionable: a refused connection says how to start the server,
an HTTP error carries the server's own message (e.g. *"Model not
found"*), and a non-JSON reply points at the endpoint setting.
Nothing is sent anywhere except the endpoint you configured.
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.