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