- Monitor rows built once; bars and the workflow progress bar animate per frame via transform, text at 10Hz — no more per-frame DOM rebuilds or throwaway canvases
- Monitor panel on its own compositor layer so its redraw no longer repaints the canvas beneath it
- GuLuLu pixel hit-test guards against an unloaded or broken image (from EndoTheDev's PR #22)
updatePixelDataCache() drew the sprite into an offscreen canvas and read
its alpha channel without checking that the image had actually decoded.
Read too early, every pixel came back transparent and the sprite could
not be clicked or dragged until something else forced a recompute; with
a broken image (the 404 from the folder-casing bug, or a user-supplied
replacement) drawImage threw InvalidStateError into the mousemove
handlers.
Skip the update until img.complete && naturalWidth > 0, clear the cache
on img.onerror, and catch failures from drawImage/getImageData. Every
consumer of pixelDataCache already tolerates null.
Guards proposed by EndoTheDev in PR #22; the path change from that PR was
superseded by the import.meta.url fix in 0.71.1.
Same treatment as the resource bars: the fill is a scaleX transform
updated every frame from the eased percentage, while the text and dot
keep the 10Hz refresh.
A fixed element repainting every frame on top of the graph canvas shared
that canvas's layer, so each repaint invalidated the region beneath it
too. will-change: transform promotes the panel to its own layer; the
canvas underneath no longer repaints on the monitor's account.
Rows are created once and kept. Each frame only sets the fill's
transform: scaleX(), which the compositor handles without layout, so the
bars now glide with the curves instead of stepping every 500ms. Text is
refreshed at 10Hz — faster is unreadable and each change relayouts its
row. Truncation measures through one cached context; the throwaway
canvas per row per frame is gone. Measured: 0.007ms per bar update,
0.011ms per text update, down from 0.64ms per rebuild.
displayInterval was 0, so updateDisplay() and updateWorkflowProgress()
ran on every animation frame: innerHTML rebuilt for all rows and a fresh
canvas created per row just to measure text — ~300 canvases and 60
forced layouts a second, for data that only changes every 0.5–2s. Update
every 500ms and keep one measuring context.
- pyproject Repository now spelled github.com/kk8bit/KayTool, matching
ComfyUI-Manager's custom-node-list entry so the registry and list
entries merge instead of appearing as two packs (one versionless,
one with -1 stars, both ranked near the bottom)
- Resource monitor's RAM figure now derives from total - available so
it agrees with its own percentage (macOS showed 10.1/24GB at 68%)
psutil's virtual_memory().used counts only active+wired on macOS, while
.percent is defined as (total - available) / total. Showing one next to
the other produced readings like 10.1/24GB (68%). Derive the used figure
from total - available so both describe the same thing; it also lines up
with Activity Monitor's Memory Used.
- Translation nodes moved to kk8bit/ComfyUI-kaytool-translate (Git install, same node ids)
- Resource monitor polls from the frontend; no resident backend task, no env writes
- Monitor chart rendered at device pixel ratio; data rows always fit
- Load Image Folder emits filenames; Custom Save Image can keep them (exact_filename)
- AbcMath gains trig / log / exp and pi, e, tau
- Custom Save Image writes to ComfyUI's output directory, honouring --output-directory
- Translator requests time out; Workflow PNG restores the canvas on failure
- Monitor no longer dies when pynvml is missing
The Comfy Registry's policy-v0.2 scanner flags every version that makes
a network request for manual review. AIO, Tencent and Baidu translaters
are nothing but a network request, and they were the only network code
left in this package — the other 24 nodes never touch it. Keeping them
here meant every release of KayTool waited on a human.
They now live unchanged in kk8bit/ComfyUI-kaytool-translate, installed
from Git, with identical node identifiers so existing workflows recover
by installing that package. requests leaves the dependency list.
The monitor ran a resident asyncio task that sampled psutil/nvml every
0.5–2s and pushed the result over ComfyUI's WebSocket — whether or not
any panel was open, whether or not the tab was even in the foreground.
It is now a plain GET /kaytool/resources that the panel polls at the same
adaptive rate while it is visible and the page is in the foreground. A
200ms cache dedupes concurrent tabs and keeps cpu_percent's "since last
call" baseline sane.
The RemBG loader stopped setting U2NET_HOME. rembg offers no other way to
choose its model directory, and mutating the process environment reached
every other rembg-based extension in the same ComfyUI. Models now live
where rembg puts them by default; the changelog carries the migration
note.
Event listeners in gululu.js and nodealign.js use arrow functions instead
of .bind(this).
Together these remove every finding the registry's policy-v0.2 scanner
raised against 0.71.0 except the translators' HTTP calls.
The chart canvas only ever set its width/height attributes, so on a 2x
display the browser upscaled a half-resolution bitmap and every curve
came out soft. On top of that the CSS pinned the canvas to 100px while
the backing store was computed at 119px, squashing the plot vertically
by 0.84.
Let flex layout own the canvas's CSS size (it takes whatever the data
rows leave over) and size the backing store from the laid-out rect times
devicePixelRatio, with the context transform matching so drawCurves keeps
working in CSS pixels. A ResizeObserver re-syncs the backing store when
the rows first appear or the panel is resized.
The data rows are now flex: 0 0 auto so they can no longer be squeezed
out of view; the minimum panel height goes to 150 because 90 never had
room for them in the first place.
The guard around the pynvml import was written as
except (ImportError, pynvml.NVMLError):
but when the import is what failed, the name pynvml is unbound, so
evaluating the except clause raises NameError — which that clause does not
catch. The handler blew up in precisely the situation it existed for:
Linux or Windows without pynvml installed.
It propagates out of the collector's constructor into monitor_loop, which
runs as a bare create_task, so nothing reports it beyond a "Task exception
was never retrieved" at GC time. The monitor just never produces data, and
running stays True, so a later start() takes the already-running branch.
Catch Exception instead, and wrap the loop so any failure is logged and
running is reset.
export() resized the canvas, changed its scale and offset, and only
restored them after the blob came back. Anything that threw in between —
most plausibly toBlob on a workflow large enough to exceed the browser's
canvas limit, which is exactly the workflow someone wants a PNG of — left
the user's view stuck at export dimensions until they reloaded.
restoreState() now runs in a finally, followed by a redraw.
The GuLuLu yes/no callbacks also swallowed the rejection, so the failure
looked like the button simply doing nothing. They now go through a wrapper
that logs and shows a toast naming the canvas limit as the likely cause.
requests has no default timeout, and these calls run synchronously on the
workflow thread. A server that accepts the connection and then never
replies — plausible for transmart.qq.com, which is not a public API —
blocks that thread forever: the queue behind it stalls, nothing is logged,
and there is no way to tell which node is responsible.
10s to connect, 30s to read. The existing handlers turn the resulting
exception into the node's usual error.
The Baidu node's request sat outside its try block, so a network failure
escaped as a raw requests exception instead of the readable message the
other two produce. Wrapped to match.
get_output_directory() built its path from os.getcwd(), so where images
landed depended on where ComfyUI was launched from rather than on where
ComfyUI keeps its outputs, and --output-directory was ignored entirely.
Launching from anywhere other than the ComfyUI root scattered saves into
an output/ folder next to whatever the shell happened to be in.
Ask folder_paths, which is already imported. The Custom_Save_Image
subfolder is kept so existing paths still resolve.
The expression evaluator whitelists the callables it will dispatch, so
extending it is a matter of adding entries: the trig family, their
hyperbolic and inverse forms, radians/degrees conversion, sqrt, exp, the
logarithms, floor, ceil, hypot and abs. Constants pi, e and tau resolve
the same way a, b and c do.
Trigonometry is in radians, as in Python, so sin(radians(a)) is how you
feed it degrees.
The result guard only caught NaN. log(0) and similar produce infinities
that int() then refuses, so infinities are folded to 0 alongside NaN.
Closes#12.
Load Image Folder now also returns the source filenames, one per image,
appended to the existing list outputs so they stay index-aligned (an
image that fails to load is skipped from all three, otherwise downstream
pairing silently shifts).
Custom Save Image gained an exact_filename toggle. It always appended a
timestamp and an index to the prefix, so feeding it an original name
still produced photo_1726824000000_000.png. With the toggle on the prefix
is the filename.
The name reaching that toggle comes from an input, so it is reduced to a
basename before use and can't escape the output directory, and a
colliding name gets a _1 suffix rather than overwriting what's there.
Closes#24.
Both took their target from nodes[0], the node that happened to be
selected first, so the same selection gave different results depending on
click order and usually looked like the nodes had shrunk for no reason.
Use the largest value instead, which is also safe against each node's own
minimum size.
Route the resize through setSize where the frontend provides it, rather
than mutating the size array in place behind the property setter.
The feature was presented as customising "the ComfyUI Logo", so people
expected the logo at the top left of the interface to change. It only
ever replaced link[rel=icon], the browser tab icon. That logo is an
inline SVG rendered by the frontend and is not something this setting
touches.
Rename the setting, its category and the README section accordingly, and
say in the tooltips that the interface logo is unaffected. No behaviour
change.
Also drops a duplicate KayTool_EnableCustomWebLogo key that the previous
commit left in every locale file.
The feature could only be turned off by clearing the uploaded image, and
its fallback pointed at /favicon.ico, which current ComfyUI does not
serve at all, so disabling it left a dead icon rather than the original.
Remember whatever favicon ComfyUI has at setup, before any job has run,
and restore that when the toggle is off. If there is nothing to restore,
leave the favicon alone rather than pointing it somewhere broken.
Run and Run Group in the context menu, and the Alt+R shortcut, all
predate ComfyUI's own partial execution and now only duplicate it. Worse,
they existed only to drive a queuePrompt override that intercepted every
queue call, and that interception is what broke the official partial
execution and the preview method chosen in the UI.
Deleting the feature removes the override entirely, which is a better
answer than forwarding its arguments correctly. Set/Get, the other half
of this extension, is untouched.
Drops the KayTool.ShowRunOption and KayTool.ShiftR settings and their
translations. Closes#30, #31, #32.
The queuePrompt override forwarded only its first two arguments, but the
frontend passes partialExecutionTargets and previewMethod in a third one.
Dropping it made ComfyUI's own partial execution run the entire workflow
and disabled the preview method chosen in the UI. Forward arguments as
given. Closes#30, #31, #32.
The Monitor button applied its enabled setting only from the setting's
onChange handler, so a page load always appended it visible regardless of
the stored value. Apply the setting when the button is created. Closes
#19.
A missing pilgram shelled out to `pip install`, which raises in any
environment without a pip module (uv-created virtualenvs, among others)
and takes the whole package's import down with it. Degrade to an empty
filter list and a warning instead.
Also declare pilgram and requests in pyproject.toml, which registry
installs read instead of requirements.txt.
Docking into the top bar meant either living inside the Run button's card
and being dragged around with it, or snapping to a row whose geometry we
have to infer from the frontend's markup. Neither is worth the coupling
for a toolbar the user repositions often, so the toolbar now simply stays
wherever it is dropped.
Removes the band probing, the dock guide, the Run card avoidance and the
resize tracking that existed only to keep a docked toolbar aligned.
waitForElements() polled for .comfyui-menu, which the current frontend no
longer renders, so its promise never resolved. init() was stuck on that
await forever: the toolbar was never built and the Shift+WASD shortcuts
were never bound, while the requestAnimationFrame poll kept spinning for
the life of the session.
Docking into the DOM is no longer viable either way. The topbar is Vue
managed, so a card injected next to the Run button is wiped on the next
re-render, and the supported legacy slot (app.menu.element) lives inside
the Run button's own card, which would drag our toolbar around with it.
So the toolbar is now always a fixed element on document.body, like the
resource monitor, and docking is positional rather than structural: drop
it anywhere over the top bar row and it aligns to that row's centre,
keeping the x the user chose and stepping aside from the Run card. The
band geometry is read through data-testid hooks with a plain fallback, so
a future frontend redesign degrades to floating instead of vanishing.
LGraphNode.prototype now defines title/type as accessors. Object.assign
uses [[Set]], so assigning a plain object containing those keys onto
nodeType.prototype invoked the inherited setter with `this` bound to the
prototype, which has no _state, and threw a TypeError.
The throw aborted beforeRegisterNodeDef, so none of the node's methods
(onExecuted, onResize, getExtraMenuOptions, onDrawForeground,
onMouseMove, onNodeCreated) were ever installed.
title and type are set by ComfyUI's node registration anyway, so drop
them from the assigned object. The hardcoded title also shadowed the
localized display name.
The GuLuLu gif path was hardcoded as /extensions/kaytool/, but ComfyUI
serves the web directory under the custom_nodes folder name and aiohttp
route matching is case-sensitive. Installs that end up with a KayTool
folder (e.g. via ComfyUI-Manager, whose node list points at the
capitalized repo URL) got a 404 for the gif.
Derive the path from import.meta.url so it resolves correctly whatever
the folder is named.