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.
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.
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.
- 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.
- 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.
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.
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.
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 audit compared every node against ComfyUI core and the major packs;
this lands its outcomes in one branch.
On-node visual editing (the old Codex-pack interaction, rebuilt):
- New Load Image + Pad: image loader with whole-edge pad dragging on the
node canvas, align-to-multiple rounding, and a megapixel target that
rescales the source before padding so the mask seam stays binary.
- Pad Image gains the same handle canvas, fed by an execution-time
snapshot of its input; wireframe with diagnostic empty states before
the first run. Widgets, outputs, and saved workflows unchanged.
- Image Crop + Rotate + Pad's compact panel is now a live stage - crop,
pad, and rotate handles on the node, one shared implementation with
the full-screen editor so the two surfaces cannot drift.
Feature gaps closed against the ecosystem:
- Crop For Inpaint: target_megapixels, selectable rescale_algorithm
(recorded in the stitcher for the paste-back), and extend_* inputs
that grow the frame itself for outpainting.
- Save Video: format choice - mp4 h264, mp4 h265, webm vp9 with Opus
(resampled to 48 kHz); CRF drives all three.
- Load Video: every_nth thinning (fps output divides to match) and a
max_frames cap that ends the decode early.
- LoRA Loader: saved stack templates behind a bar button, and a console
warning when a LoRA's base-model family disagrees with the checkpoint.
- LM Studio Chat: history in/out for multi-turn chains and a json_schema
widget for structured replies.
- Drop Shadow: multiply blend and a shadow_mask output. Align Image:
offset outputs for un-aligning after sampling. Color Match:
first_frame reference mode as a one-node video flicker fix.
Removed (backed up in git history): Select Frame Range and the Video
Bundle family - core ImageFromBatch and the core VIDEO wire now cover
both jobs. Dead NODE_ID module variables cleaned out of every node file.
455 Python tests, 216 JS tests, validate_nodes.py green on 19 node files.
Frame Chooser: the notice inflated the grid's auto flex basis and squeezed
the action buttons into the bottom clip - the grid now flexes from a zero
basis, the fixed rows never shrink, and idle gets a little more room.
LoRA Loader: the empty state is just 'No LoRAs yet' and is itself a click
target that adds a row and opens the picker.
Align Image: pad mode gained an anchor (pad_position), a fill choice
(replicate or color), and pad_color - all hidden outside pad mode, the
color field hidden until the color fill is chosen.
LM Studio Chat: the gear moved off the node's tail into an endpoint
toolbar - status light, Test (asks the server for its model list through a
new /ausboss/lmstudio/models route; green/red + toast), Models (pick from
the server's list straight into the model field), gear. A visible
unload_llm switch frees the LLM right after the reply via LM Studio's JIT
ttl, overriding the gear's idle timer.
top-p, top-k, min-p, repeat and presence penalty, thinking on/off via
chat_template_kwargs with custom reasoning tags, LM Studio idle-unload
TTL, and free-ComfyUI-VRAM-first. Values ride hidden standard widgets so
they save with the workflow and reach the API; neutral defaults are
omitted from the request body.
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.
The audit behind the LoRA fix found two residual gaps and closed the
policy hole that let them hide. Frame Chooser declared its minimum
width to the modern layout but not through options.minNodeSize, the
path older frontends read - so on those, the node could still be
resized under the panel, which holds MIN_WIDTH and pokes past the
border exactly like the LoRA row did; the overflow clip cannot help
there because it cuts at the panel's edge, not the node's. The
input-preview thumbnail now clips at its root as well, making every
root uniform.
The policy test now requires the full set of every resizable panel:
border-box and an overflow clip reaching the root class (a blanket
group selector counts), plus a minimum width on BOTH layout paths -
computeLayoutSize for modern frontends and options.minNodeSize for
older ones. Against main's pre-fix LoRA file it fails 4 of 6, and
stashing the chooser change fails 1, so both halves are proven to
catch what they were written for.
The strength box and info button hung past the node's right edge, two
gaps stacked: the panel's 10px padding rendered outside the width the
frontend allocates (content-box), and nothing declared a minimum width
to the modern layout - computeSize clamped the PANEL to 300px, but
without computeLayoutSize the NODE could be resized narrower, and the
row's fixed-width trailing controls kept their size while the picker
shrank to nothing.
Every other DOM panel in the pack already carries the full guard set -
border-box, an overflow clip, computeLayoutSize minWidth, minNodeSize -
through the shared video root or its own CSS; the LoRA loader was the
one that had none of it. It now matches the house pattern, sharing one
PANEL_MIN_WIDTH constant across every sizing path the frontends
consult so the panel and the node can never disagree about how narrow
is legal.
tests/panel_guards.test.mjs makes the pattern policy: every entry that
calls addDOMWidget must carry the guards or mount the shared root that
does. Against the pre-fix file it fails 3 of 5.