The mark shipped as an opaque 400x400 square, which reads as a hard tile on the Manager's pack card. The frontend's PackBanner draws the icon twice into a 7:3 box: once bg-cover plus blur(10px) at opacity-30 as a backdrop, once object-contain on top. bg-cover crops to the centre and zooms about 2.3x, so it samples mostly glow and lifts the surround to ~47, while the contained copy still shows the icon's own near-black corners at ~17. Measured off a card screenshot, that is a 26-34 level step at the tile edge; the same measurement on rgthree-comfy, whose icon is a transparent SVG, is 2-3 levels. Alpha now comes from luminance, which is the right derivation for artwork that is additive glow on near-black, and it removes the edge outright. Checked against a local reimplementation of PackBanner that reproduces the screenshot's tile corners exactly. The trade is that the pale mark washes out on ComfyUI's light theme. Taken deliberately: the dark theme is the common case, and it is what the comparable packs do. A dark backing disc was the alternative and reads as a grey blob on light instead. No version bump. The registry stores the icon URL rather than the bytes and the Manager hotlinks it, so replacing the file on main restyles every card; only a changed URL would need a republish. Also worth recording: Banner is deliberately still unset, because PackBanner resolves banner_url || icon, so a banner replaces the mark instead of sitting behind it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015p88qHFcfungDkCbG1n7z2
7.7 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
A single-file ComfyUI custom node package. All code lives in __init__.py; there
is no build step, test suite, or linter config. Three nodes —
ResolutionPreset, ResolutionSelectorMP and ResolutionAlignToGrid — all pure
integer maths with no torch/numpy involvement.
ResolutionPreset's combo options are generated at import time by
build_presets() from the same lattice_step() the solver uses, so the list can
never disagree with solve_exact(). Do not hand-edit resolutions into it. The
per-ratio grid choice (coarsest alignment yielding PRESET_MIN_ENTRIES sizes)
exists because fine-lattice ratios like 1:1 have L=16 and would otherwise emit
~105 entries; changing PRESET_MIN_MP / PRESET_MAX_MP / PRESET_MIN_ENTRIES
silently reshapes every label, and labels are the public API — a saved
workflow stores the selected label string, so altering the format or the tuning
constants breaks existing graphs.
Registered through ComfyUI's V3 schema API, not the legacy
NODE_CLASS_MAPPINGS dict: comfy_entrypoint() returns a ComfyExtension whose
get_node_list() yields IO.ComfyNode subclasses. Inputs are declared in
define_schema() and arrive as named kwargs to execute().
Local environment
- ComfyUI checkout:
/home/dreevelle/comfy/ComfyUI - Python venv:
/home/dreevelle/comfy-env/bin/python - The package is published, and
ComfyUI/custom_nodes/comfyui-resolution-tools(lowercase, the registry id) is a ComfyUI-Manager install that tracks registry releases. This working tree is not wired into ComfyUI.
To test a change before releasing it, symlink this tree in under its CamelCase name so it doesn't collide with the Manager install:
ln -sfn /home/dreevelle/Projects/ComfyUI-ResolutionTools \
/home/dreevelle/comfy/ComfyUI/custom_nodes/ComfyUI-ResolutionTools
Remove that symlink before doing anything in ComfyUI-Manager for this
package. Manager's remove/replace path does shutil.rmtree on the resolved
path, so it deletes the symlink target's contents instead of unlinking — on
2026-08-09 that wiped this working tree, .git included, and it had to be
restored by re-cloning. Commit and push before any Manager operation, and never
leave a symlinked dev copy in place while installing or updating the published
version.
Verifying changes
__init__.py imports comfy_api.latest at module scope, so it needs ComfyUI on
sys.path — but the maths functions are module-level and dependency-free, so
they can be exercised directly:
cd /home/dreevelle/comfy/ComfyUI && PYTHONPATH=/home/dreevelle/comfy/ComfyUI \
/home/dreevelle/comfy-env/bin/python -c "
import importlib.util
spec = importlib.util.spec_from_file_location('rt', '/home/dreevelle/Projects/ComfyUI-ResolutionTools/__init__.py')
m = importlib.util.module_from_spec(spec); spec.loader.exec_module(m)
print(m.solve_exact(9, 16, 16, 2.36e6)) # -> (1152, 2048)
"
Two invariants any change to solve_exact must preserve, and which are worth
re-fuzzing over random (ratio, alignment, target) triples:
width % alignment == 0 and height % alignment == 0width * h_ratio == height * w_ratio(ratio held exactly, no drift)
Known-good anchors: 9:16 @ m=16, 2.36 MP -> 1152x2048;
16:9 @ m=32, 1.33 MP -> 1536x864; 21:9 @ m=16, 2.37 MP -> 2352x1008.
Why the maths is what it is
Real megapixels. Core's comfy_extras/nodes_resolution.py:78 uses
megapixels * 1024 * 1024. That's the SDXL-era convention where "1 MP" meant
1024x1024. This package deliberately diverges to * 1_000_000. Do not "fix"
this to match core — the divergence is the point, and the README documents it.
Lattice solving. lattice_step() computes the smallest scale increment that
keeps both axes on the alignment grid while holding an exact ratio. Core rounds
each axis independently, which drifts the ratio (9:16 at 2.36 MP becomes 0.5649
instead of 0.5625). solve_nearest() preserves core's behaviour for when the
lattice is too coarse; it is opt-in via exact_ratio=False.
Alignment = VAE spatial downscale x DiT patch size. Verified against the ComfyUI source rather than assumed:
- Krea 2 -> 16.
latent_formats.LatentFormat.spacial_downscale_ratio = 8(inherited byWan21, whichsupported_models.Krea2uses) xpatch=2incomfy/ldm/krea2/model.py. - MiniMax H3 -> 32.
latent_formats.MiniMaxH3Video.spacial_downscale_ratio = 16xpatch_size=(1, 2, 2)incomfy/ldm/minimax/model.py. - SDXL/SD1.5 -> 64. UNet, no patchify; three downsample stages on an 8x VAE.
The failure mode when unaligned is comfy.ldm.common_dit.pad_to_patch_size,
which pads with padding_mode="circular" and appends only at the end — hence a
bottom/right band, never top/left. If a future model changes VAE or patch size,
the alignment table in the README and ALIGNMENT_TOOLTIP both need updating.
Conventions
- Version is duplicated in
__version__(__init__.py) and[project].version(pyproject.toml) — bump both. dependenciesstays empty; this package is stdlib-only by design.- Node IDs (
ResolutionSelectorMP,ResolutionAlignToGrid) are the public API. Renaming one breaks every saved workflow that uses it.
Releasing
Intended for the Comfy Registry as comfyui-resolution-tools under publisher
dreevelle, same setup as ComfyUI-FastImageSequence.
Changing [project].version in pyproject.toml on main publishes a
release. .github/workflows/publish_action.yml watches that path and runs
Comfy-Org/publish-node-action with the REGISTRY_ACCESS_TOKEN repo secret.
Don't touch the version field unless a release is intended; republishing an
existing version fails with 400 The node version already exists.
.comfyignore keeps CLAUDE.md and .github/ out of the published archive.
comfy node validate runs the registry checks locally; comfy node pack
produces the exact archive that would be uploaded.
assets/icon.png is the registry icon, pointed at by [tool.comfy] Icon as a
raw GitHub URL on main. The registry stores the URL, not the bytes, and the
Manager hotlinks it, so replacing this file on main restyles every card with
no republish. Only changing the URL needs a version bump, because icon is
node-level metadata written as a side effect of publishing a version.
The background is transparent on purpose. The frontend's PackBanner draws
the icon twice into a 7:3 box — once as a backdrop, bg-cover plus blur(10px)
at opacity-30, and once object-contain on top. An opaque icon therefore shows
as a hard rectangle: bg-cover crops to the centre and zooms ~2.3x, so it samples
mostly glow and lifts the surround to ~47, while the contained copy still shows
the icon's own near-black corners at ~17. Deriving alpha from luminance removes
the edge completely, and it is the right derivation because the artwork is
additive glow on near-black. The cost, accepted deliberately, is that the pale
mark washes out on ComfyUI's light theme.
Regenerate from the brand original, capped at the registry's 400x400 square maximum. Downscale in linear light; the naive sRGB-space resize dulls the ring stroke and blurs the bar separations inside the D:
magick ~/Projects/alora/dev/assets/brand/dreevelle_pfp.png \
-colorspace RGB -filter Lanczos -resize 400x400 -colorspace sRGB \
\( +clone -grayscale Rec601Luma -level 7.45%,100% \) \
-alpha off -compose CopyOpacity -composite \
-background none -alpha Background -strip assets/icon.png
oxipng -o max --strip safe assets/icon.png
Do not set [tool.comfy] Banner. PackBanner resolves banner_url || icon, so
a banner replaces the mark rather than sitting behind it.