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.
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.
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.
- 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.
The ComfyUI version floor never reached the registry. comfy-cli reads it from
a [tool.comfy] key named requires-comfyui; the pack declared
supported_comfyui_version, which the parser ignores, so 1.1.0 published with
an empty floor and Manager had nothing to refuse an incompatible install on.
Also declares Operating System :: OS Independent, which is where the registry
sources supported_os from, and publishes the license as a readable name rather
than the literal string {"file": "LICENSE"} that a file reference
serializes to. Accelerator classifiers stay off rather than asserting hardware
nobody has tested.
Verified by parsing pyproject.toml through comfy-cli itself: the floor now
reads >=0.27.1 and supported_os reads ['OS Independent']. No node behaviour
changes.
Closes the Unreleased section into 1.1.0 and syncs the version across
pyproject.toml and AUSBOSS_JS_VERSION, which release_preflight.py compares so
a fresh install never trips the stale-browser warning.
First version published to the Comfy Registry. 1.0.0 was never tagged or
released anywhere, so it stays as changelog history rather than becoming the
registry's first entry describing a fifth of what it contains.
The frontend sizes a DOM widget's wrapper as widget.width ?? node.width
(GraphView, useDomWidget updateWidgets), and LiteGraph's layout plants
widget.width during draws. Once planted it outranks the node width
forever: resize the node narrower and the wrapper keeps the old width,
parking the panel's right edge - and the LoRA row's strength and info
controls - outside the border. None of the containment guards could
reach it: border-box and the overflow clip act at the wrapper's edge,
which is precisely the thing that is too wide, and the minimum-width
paths only stop the node going BELOW a floor, not the wrapper staying
ABOVE the node.
Diagnosed from a console measurement in the reporting browser (wrapper
styled 470px on a 328px node, scale 1.21) and reproduced exactly by
planting widget.width = 470: overflow +183px. keepDomWidgetWidthAuto
defines width as a discard-writes accessor, so the frontend's lookup
stays on its node-width fallback for the life of the widget. All six
DOM panels call it, the policy test requires it, and the fix was
verified end-to-end in a live classic-renderer session: with the plant
attempted, the wrapper tracked the node through narrow, add-row,
linked, grow and shrink at -13px inside the border throughout.
Frame Chooser, mostly. A pause did not survive a workflow load: LiteGraph
clears a graph by removing every node, so undo, a tab switch, Clear
Workflow and opening another file all fired the teardown a deleted node
uses to release its pause, and killed a run that was still going. That
now only fires for a real deletion. Answering "keep all" wrote the batch
out as 1,2,...,N into pick_list, so the next run dropped whatever the
enumeration did not reach or failed outright on a shorter clip; it
writes the empty answer it was given. And pick_list is only written back
under "keep last selection" - it pre-answers the node, so filling it in
automatically meant a chooser left at the default paused exactly once.
Refine Mask: smooth applies only the change it made to the jaggy edge
rather than binarizing everything, so an already-soft matte keeps its
alpha (binary masks stay bit-identical), and blur now reaches the
matting solve instead of being thresholded back out of the trimap.
Frame Interpolate's copy path honours batch_size; it gathered every
copied frame at once, which on a long clip at an integer multiple was a
multi-gigabyte allocation on the frames device that no setting bounded.
Subgraph execution ids are resolved by walking the chain instead of
stripping the prefix, which could match an unrelated node with the same
number. Status and runtime badges appear inside subgraphs again, and a
subgraph pause renders on the node that actually paused.
Also: the Custom colour scheme no longer strips every node's colour when
its stored colour is unreadable; Drop Shadow and Pad Image name their
own widget for an unreadable colour instead of blaming Transform; a
non-ASCII token and a non-object JSON body answer 400 rather than
throwing a 500 and stranding the pause; pick_list rejects Unicode
digits, which used to raise during validation or silently read as a
frame nobody typed.
Tooling: validate_nodes parses NODE_MODULES rather than matching it
anywhere in the text, and an unregistered module is an error - it used
to exit 0 printing nothing. It also refuses mapping keys declared
outside nodes/, which is how the registry fixtures came to be advertised
to ComfyUI-Manager as installable nodes; they are .py.txt now. And the
video test no longer leaves ComfyUI on sys.path, which was flipping
every fail-soft seam for whatever ran next.
Project structure modeled on ComfyUI-Pixaroma (MIT) with original code:
fail-soft module loader in __init__.py, one file per node under nodes/,
per-node frontend folders under js/ with shared .mjs utilities, offline
validation script, docs, and a smoke-test workflow.
Phase 1 placeholder nodes: Text Box, Show Text (with JS frontend),
Random Line, Image Dimensions.