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.
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.
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.
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.
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.
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.
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.
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.
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.
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.