74 KiB
Changelog
All notable changes to this project should be documented in this file.
The format is inspired by Keep a Changelog and SemVer.
[Unreleased]
Added
- Load dialog asks YES/NO when an auto-save is newer than the saved
version. Click any preset row in Snapshot → Load and, if the
matching
<preset>_autosave/periodic.jsonis more recent than the named save's on-disk mtime, the confirm dialog upgrades from a single "Load preset" button to a YES/NO question: "Auto-save is NEWER than the saved version. Do you want to load the auto-saved version?". YES loads the auto-save, NO loads the older deliberate save (the row the user originally clicked), Esc cancels. Both paths run the standard pre-load auto-save protection so either choice is reversible. Replaces the earlier inline "↻ Newer auto-save" subline on the row itself — putting the question on the row asked the user to compare two timestamps before they had committed to loading anything; moving it to the post-click modal puts the choice exactly where the user has already decided to load and just needs to pick which version. The list rows render as before (one timestamp, one × button) so the library list stays scannable. Server-side:/koolook/presets/listaugments root rows withlatestAutosaveMtimevia one extrastat()per row — no file read, cheap on large libraries. Aligns the system with the mental model: named saves stay deliberate (only updated on Save / Quick Save), and the auto-save is treated as a recovery copy the system surfaces at decision time rather than a parallel save destination the user has to remember to check. - Bundled visual onboarding guide for the Kforge Labs sidebar. The
existing Tools-row
Hhelp shortcut now opensweb/guide/index.html, an offline visual guide that frames Snapshots as a reusable project kit for favorite nodes, cross-sidebar search, saved workflows, modules, tags, autosaves, and starter presets. Its static styling borrows the dark, token-driven card language from the ForgeFlow sibling project's design docs. The first version ships annotated mock images underweb/guide/img/so they can be replaced by real ComfyUI screenshots later without changing the guide structure. Linked from the README and user guide index for users browsing docs outside ComfyUI. - Subgraph-aware module saves. Selection saves now carry the
transitively referenced
definitions.subgraphsentries for selected ComfyUI subgraph wrapper nodes, so the saved workflow has the definitions available for native Load / later insert attempts. - Dev-sync build-tag footer at the bottom of the Kforge Labs sidebar.
scripts/sync_to_dev.py --scope "<short scope>"now writes a tinyweb/_dev_build.jsonnext to the sidebar JS. The sidebar fetches it on render and emits a discreet two-line footer:dev <sha> · <time>(line 1, SHA at 13px monospace) + the scope (line 2, italic proportional). Visible-in-app counterpart to the chat-report convention from #95 — when the maintainer is juggling multiple parallel worktree sessions, a glance at the footer confirms which one's code is actually live in the browser tab. Cache-busted fetch so a re-sync's new mtime reaches the browser without a hard refresh. Absent on registry installs (the JSON is dev-only and gitignored); the footer renders empty there. The--scopevalue is the same ≤10-word change-summary the agent quotes on chat-report line 2 — passing it once now feeds both surfaces. ×removability across both group modes for any visible favorite, including auto-pulled REPOS{select: "all"} nodes. Adds akoolook.autoPullHidden.v1localStorage set: clicking×on a row that came from auto-pull adds the type to the hidden set so the next render skips it (without the set, the next gather pass would re-pull the type from the registry and the click would feel like a no-op). User picks still go throughremoveFromMyPicks. Re-adding via the toolbar+button or right-click → "Add to Kforge Labs" clears the hide as a side effect ofaddToMyPicks, so users don't have to think about which list a type lives in. Same uniform "remove this from my favorites / bring it back" UX whether the node is a Koolook auto-pull or a KJNodes user-pick.- Snapshot library path browser. Snapshot Settings now has a Browse button
backed by a new
/koolook/presets/browseroute, so users can navigate to the preset folder from inside Kforge Labs instead of copy-pasting a path from an external file manager. The browser hides internal auto-save folders, supports creating one child folder under the current location, and makes the selected folder name visually prominent before the user chooses it.
Changed
- Snapshot timestamps now use 24-hour format with relative-recent tier.
The Load dialog's "saved …" line and the snapshot status-dot tooltip
previously formatted local time as 12-hour
en-US(May 8, 12:56 AM), which read visually like noon for timestamps just past midnight — the classic US "12 AM = midnight" trap. Times now render as:just now(<1 min),12 min ago(<60 min),today 14:32/yesterday 23:58(same / previous calendar day), orMay 6 14:32(older; year appended when not current). Centralized in a newformat_time.jshelper used by both the Load dialog rows and the status tooltip so future surfaces stay aligned. - Sidebar context menus now enforce a single open menu. Opening a workflow, directory, archive, or Workflows-header right-click menu now closes any previous sidebar context menu first, so repeated right-clicks cannot stack multiple floating menus with stale actions.
- "Create directory" moved off the Workflows toolbar onto right-click of
the section header. The 📂 (
pi-folder-open) button used to live in the Workflows action bar between the section label and the Save buttons. Tree- structure operations (create / rename / delete a folder) already live on right-click of the row representing the structure being mutated — the per- folder menu offers "Create subdirectory…" / "Rename directory…" / "Delete directory" — but top-level dir creation was the lone outlier sitting in the toolbar. Moving it to right-click on the "Workflows" header row makes the action bar homogeneous (only save actions: Save canvas, Save selection) and unifies the mental model: every tree-structure operation lives on right-click of the structure node it acts on. Implemented via a new optionalrootContextMenufield on section descriptors plumbed throughbuildFolder; only Workflows opts in for now (Nodes/Tags keep today's no-context-menu section header). Fresh-install path is unchanged — the Save modal's "+ New directory…" entry still creates the first directory when the workflow store is empty. Closes #104. - Kforge Labs sidebar action icons now use a first-pass ForgeFlow-style
icon set. Snapshot Load / Save, Tools export / install / help, the
missing-pack canvas handoff, repo-mode grouping, and whole-canvas workflow
save now use the approved compact letter / simple-glyph treatment from
docs/designs/sidebar-icon-proposals.html, reducing repeated generic cloud / grid / floppy symbols while leaving all actions and tooltips unchanged. The existing Tools-rowHhelp shortcut is now wired to the bundled visual onboarding guide. - Node help text tightened for scannability. The Easy Resize description and HDR VAE encode/decode tooltips now use shorter, action-oriented text so the ComfyUI node help stays readable while detailed behavior remains in the user guide.
- README refocused as a short product front door. The repo landing page now leads with the Kforge Labs sidebar, Snapshots, search, workflows, and recovery, then links out to the deeper user/maintainer/fork docs instead of carrying long maintainer workflow and implementation sections inline.
- Node-list hover previews now stay at the intended 300px mock-node width.
The preview card previously had only a
min-widthplus viewportmax-width, so long node descriptions could set the card's intrinsic width and stretch the hover overlay across the canvas. The card now uses a fixed 300px preferred width clamped to the viewport, keeping labels and descriptions contained. Its surface colors also now track ComfyUI's native node charcoal grades more closely, so mock previews feel like lightweight nodes instead of black sidebar tooltips. - Theme mode now mirrors repo-mode population — auto-pulled
REPOS{select: "all"} nodes appear alongside user picks (deduped),
filtered by the new auto-pull hidden set. Supersedes the earlier
[Unreleased]"Picks-only (no REPOS-driven auto-pulled candidates)" intent below: the picks-only model dropped the entire Koolook pack from theme mode on a stock install, which silently violated the "my favorites" mental model (the user expected their visible favorites in repo mode to also show in theme mode, just regrouped). With the new × removability, the user can still curate exactly what they see — without the discoverability cost of dropping auto-pull entirely. - Pack-name badges restored on theme-mode leaves. Small dim
Koolook/KJNodesetc. on the right of each row, same opacity as the search-flatten breadcrumb. Memory aid for "where did this node come from?" — useful when the user is learning their favorites pack-by-pack. Reverts (for theme mode only) the earlier[Unreleased]removal further down; search-flatten still skips the badge since its breadcrumb prefix already conveys origin. - Snapshot status hover and path display tightened. The status tooltip now shows only a central-time timestamp and the snapshot library location, and snapshot library path lines wrap to show the full path instead of truncating.
- Snapshot Settings save state clarified. After browsing to a new folder, the Save button now becomes an explicit dirty "Save update" action; once the path is persisted it turns back into a disabled gray "Saved" state. Load and Settings dialogs now show the active library folder name first, with the full path underneath; after changing folders, Settings warns that the current snapshot still needs Save / Quick Save to be written into the new location.
- Sidebar second mode — "Theme" instead of "Category". The sitemap-icon
toggle in the Nodes action row now groups picks by semantic theme
rather than by raw CATEGORY first-segment. The new algorithm strips the
source pack's name from the front of each pick's CATEGORY (via the
existing
findPackPathForTypeprecedence) before grouping, soKJNodes/image/Get,Koolook/Image/EasyResize, andimage/fooall collapse to one top-levelimagefolder. Each theme bucket renders as a flat sorted list — no further sub-folders. Picks-only (no REPOS-driven auto-pulled candidates) so the second mode genuinely reflects "my image-related favorites" rather than "every image-shaped node ComfyUI knows about." Synthetic buckets(unresolved)/(uncategorized)survive as before. - Pack-name badges removed from leaf rows. The small dim labels
(
Koolook,KJNodes,EasyUse) that used to render at the right edge of every leaf in theme mode and in search-flatten are gone. The breadcrumb prefix on search-flatten rows already conveys origin in a cleaner form, and theme mode by definition doesn't care about source pack — the badge was redundant.makeNodeLeafRowno longer accepts apackBadgeparameter; CSS rule.koolook-pack-badgeremoved fromconstants.js.
Fixed
- Snapshot status reads "auto-saved" — not "saved" — after restoring
from an auto-save. Previously, restoring
<preset>_autosave/ periodic.json(via the new YES path on the Load dialog OR the long-existing Recovery section) calledmarkStateSaved(), which baselined the named-save fingerprint to the auto-save's content. The dot turned green even though<preset>.jsonon disk still held the OLDER deliberate save — blurring the deliberate-save model the Path C UX deliberately reinforces. NewmarkStateAutosaved()export insnapshot.jsbaselines the periodic fingerprint instead, so the dot reads "auto-saved" (blue) until the user explicitly Quick Saves or Saves the restored state into the named file. Toast message also updated to nudge:Restored auto-save of "Foo" — Quick Save to commit to the named file.Fixes the regression flagged on PR #111 review.
Changed
- Load dialog header + Recovery group headers now share a layout. The
library section and each
<preset>_autosavegroup header now use the same two-line format (title + full path) and the same📂 Openbutton styling — visual consistency across both surfaces. Concrete changes:- Saved-time on every row. Regular snapshot rows now show
saved May 7, 11:53 PM(from the file's mtime, threaded throughloadPreviewfrom the listing endpoint) instead of justexported 07/05/2026. Recovery rows already had this format; both row types now read consistently. Falls back toexported <date>if mtime isn't available (defensive — covers any caller that doesn't carry listing metadata). - Recovery group headers now show the full subdir path under the
subdir name, mirroring the library header's
Library folder: X/<full path>pair. ReusesrenderLibraryLocationwith a newtitleoverride so the same CSS styling applies without the "Library folder:" prefix. - Library
📂 Openbutton now useskoolook-snapshot-row-btn(the smaller styling) to match the per-group Open button. Waskoolook-modal-button(larger) before — visually mismatched. formatAutosaveMetacollapsed back intoformatPreviewMetasince the format is now identical across row types.
- Saved-time on every row. Regular snapshot rows now show
Added
- Offline-fallback banner now has Restore + Discard actions — no more
permanent guilt trip. The red
criticalToastthat fires when localStorage holds a stale outage-fallback blob used to be a one-way street: Copy details, Dismiss, repeat next page load forever. Two buttons close the loop:- Restore as snapshot wraps the offline workflows into a full
snapshot envelope (live picks bundled in so a later Load wouldn't
wipe them) and writes it as
recovered-<iso>.jsoninto the snapshot library, then clears the localStorage key. The user lands on a durable, in-UI-visible artifact they can inspect, Load, or selectively cherry-pick from. Banner self-extinguishes on success. - Discard offline copy asks for confirmation, then clears the localStorage key and dismisses the banner. For the case where the user has already verified nothing is missing.
- The toast is now wired from
koolook_sidebar.js(the entry point) rather than fromloadWorkflowsStore()itself — the action handlers needwritePreset/loadUserPicks/showConfirmModal, and pulling those into the workflows store would create a circular import. criticalToastnow accepts anactions: [{label, onClick, primary?, busyLabel?}]array; existing callers ({ copyText }and string- only) keep working unchanged.
- Restore as snapshot wraps the offline workflows into a full
snapshot envelope (live picks bundled in so a later Load wouldn't
wipe them) and writes it as
Added
- Recovery section deep-links to the file system + per-row save time.
The Load dialog's Recovery auto-saves section now exposes everything
a power user needs to navigate the autosave subfolders directly:
📂 Openbutton at the top of the Load dialog opens the snapshot library folder in the OS file manager.📂 Openbutton on each<preset>_autosavegroup header opens that specific subfolder, so the user lands on the exact recovery files for that preset (with mtimes visible to Finder / Explorer sort).- Per-row save time replaces "exported <date>". Recovery
rows now show
saved May 7, 2:34 PMderived from the file's mtime — strictly more useful than the snapshot's self-reportedexportedAtfor telling "which pre-load is freshest." Mtime also survives out-of-band file copies in Finder. - Bare filename instead of redundant displayName. Each row now
renders the actual filename (
pre_load_2026-05-07T14-32-…,periodic) instead of the noisyPeriodic auto-save · <subdir> · <iso>string that pushed the timestamp off-screen. - New server route
POST /koolook/presets/reveal[?dir=<subdir>]powers both buttons via the platform-appropriate file-manager command (openon macOS,explorer.exeon Windows,xdg-openon Linux). Path-traversal grounded at the library base via the same_resolve_targetcheck the file-write routes use.
Fixed
- Insert pre-flight toast now surfaces both pack misses AND subgraph misses when a saved workflow has both, in plain English. Previously, the "all-misses-are-subgraph-UUIDs" early-return only fired when every missing type was a UUID; mixed cases fell through to a single "install the required pack(s)" message that listed UUIDs alongside pack types but mentioned nothing about the registration step the subgraph also needs. Users installed the pack, retried Insert, and hit a second unexplained "still missing" failure. Now the toast branches three ways — pure-pack / pure-subgraph / mixed — and the mixed case names both fixes upfront as a numbered two-step ("install pack(s) + right-click → Load it once"). Wording also de-jargoned — "subgraph definition not registered" was technically accurate but confused users who just wanted to know what to do. Engineer-speak stays in code comments and console.warn for devs; user-facing copy now talks in gestures and outcomes ("Right-click → Load it, then left-click Insert will work").
- Atomic preset writes — server crash mid-save can no longer corrupt
your snapshot library, and a hostile
.tmpsymlink can no longer redirect writes outside the library.POST /koolook/presets/filenow usestempfile.mkstemp(withO_CREAT | O_EXCLsemantics) to create a uniquely-named temp file inside the already-validated target directory, thenos.replaces onto the final name. A dropped connection or process kill mid-write can no longer truncate the existing good file. An attacker who plants<file>.json.tmpas a symlink pointing outside the library can no longer trick the server into following it:mkstempaborts withO_EXCLon any existing dirent at the chosen name, AND the name is randomized so the attacker can't pre-plant anything there. Covers every preset write path (named, periodic autosave, pre-load autosave, recovery snapshot) through the one route. The transient temp file is still invisible in the Load and Recovery dialogs (both list endpoints filter to.json). showConfirmModalnow exposes anonCancelcallback. Callers that wrap the modal in a Promise can now settle when the user cancels instead of leaking a pending Promise. Closes the second half of #84 (dropPlaceholdersForPackswas the original report); also removes the "Discarding…" stuck-button footgun on the offline-fallback recovery toast — Cancel now re-enables the toast button so the user can choose Restore / Copy / Dismiss instead of being trapped in a half-disabled state.- Snapshot status timestamps now follow the user's wall clock.
formatCentralTimewas hardcoded totimeZone: "America/Chicago"and surfaced aCDT/CSTstamp in the auto-saved hover tooltip regardless of where the user actually was. Renamed toformatLocalTimewith the timezone option dropped — the browser formats with the user's system timezone. Also droppedtimeZoneName: "short": it just rendered noisy abbreviations (GMT+2,CDT, …) that didn't help anyone identify "their own time." Tooltip now reads as a clean wall-clock stamp likeMay 7, 2026, 11:43 PM.
Added
- Modules — splice a saved cluster into your live canvas instead of
replacing it. Tag any saved workflow with the literal tag
moduleand the Kforge Labs sidebar starts treating it as a reusable building block: a greenpi pi-plus-circleicon replaces the file glyph, left-click inserts the cluster at the viewport center (with internal links re-created and the new nodes left selected), and a distinct hover tooltip surfaces the changed semantics. The selection-save modal now ships aSave as modulecheckbox (pre-checked for selection saves; unchecked for whole-canvas saves), which adds themoduletag inside the samepersistMutationas the entry write so a commit failure rolls back both. The right-click menu on every workflow row gains an explicit Insert into canvas entry alongside the existing Load — Load still works on every row, module-tagged or not. New primitiveinsertWorkflowOntoCanvasinweb/sidebar/canvas_io.jspre-flights every referenced node type againstLiteGraph.registered_node_typesand aborts cleanly when any are missing (a partial insert with stub nodes is worse than no insert), then deep-clones the saved graph, letsapp.graph.add()allocate fresh node ids so nothing collides with the live canvas, and recreates internal connections viaoriginNode.connect(...)so link ids are auto-allocated too — no manual link remap needed. Designed for setups like an EXR-output stack, a depth-only render cluster, or a Wan 2.2 prompt module the user wants to drop into many workflows. Full reference indocs/maintainers/workflows-sidebar.md. - Spotlight effect on add. Clicking the toolbar
+(or the canvas right-click "Add to Kforge Labs") now collapses every Nodes-section pack folder, then auto-expands just the pack + subcategory the just-saved node lives in — a pedagogical aid that helps new users internalize which pack each node belongs to. Multi-select adds light up every hit pack simultaneously. Duplicate adds (already in picks) trigger the same spotlight, since the educational reminder still applies. HelperfindPackPathForTypemirrors the gather code's REPO-precedence-then- user-pick-fallback to locate any node ID's sidebar path; new exportedspotlightAddedPicks(typeNames)fromweb/sidebar/tree.jsdoes the collapse + pin sequence and is called from both add paths. - Group-by-category mode for the Nodes section. Segmented toggle in
the Nodes action row (📦 Repository / 🌐 Category —
pi-database/pi-sitemap); choice persists in localStorage (koolook.groupMode.v1) and survives reloads, including cross-tab via the existingstorageevent listener. In repo mode (default) every pick lives under its pack; in category mode picks are regrouped by their node-classCATEGORYpath, ignoring REPOS affiliation. Categories that look the same after canonical-key normalization (lower().replace(/[\s_\-]+/g, "")) collapse into one folder —Loaders/loaders,Image/Upscaling/image/upscaling, andstyle_model/StyleModel/style modelall merge. Display label is the most-common original casing seen for that key (ties → first-seen, stable across renders because Map iteration follows insertion order). Ports of the same path under different repos all collapse together, so a Loader from Pack A and a Loader from Pack B share theLoaders/folder. Two synthetic top-level buckets surface the edge cases:(unresolved)for picks whose pack isn't currently loaded (repo mode silently dropped these — category mode keeps them visible as italic-dim rows),(uncategorized)for picks whose node class has noCATEGORY. Each leaf in category mode carries a small pack-name badge so users still see "where did this come from" — load-bearing for the existing↓ Install missingflow. Folder paths inpathStatesuse the canonical key (not the resolved display label), so an upstream casing change to the most-common label doesn't reset user expansions. Closes the "sort by topic, by what they do" piece of #46. (#73) - Flatten-on-search for the Nodes section. When the search field is
non-empty, the Nodes section drops its tree structure and renders a
flat list of matching leaves, sorted by display name, each with a
small dim-grey breadcrumb prefix (
Loaders › LoRA › LoraLoaderin category mode,Pack › Subcategory › Displayin repo mode) so the spatial-origin signal survives. Workflows and Tags sections retain their tree-under-filter behavior — the change is scoped to Nodes only. The breadcrumb collapses redundant synthetic labels: the(root)subcategory (which adds no info beyond the pack name) and any subcategory whose label duplicates the pack label (the "(uncategorized)" double-up). Always-flatten on any non-empty query is the deliberate choice over a count-threshold gate; the spatial cue from a tree is mostly already lost once a search has narrowed the set, and a flat sorted list scans faster. (#73) - Hover preview card for sidebar leaf rows. Mirrors ComfyUI's
official Node Library preview (NodePreview.vue): plain
HTML/CSS card with a colored title bar (HSL hue hashed from the
CATEGORYstring), category breadcrumb, two columns of type-colored slot dots (inputs left, outputs right), widgets section with truncated defaults, optional description. Hover a leaf for ~250ms to show; mouseleave dismisses; only one card visible at a time (module-level singleton). Card positions to the right of the row by default (12px offset), flips to the left when it'd overflow the viewport, clamps inside an 8px viewport-padding gutter. Tall cards (a node with many inputs) cap atcalc(100vh - 16px)and scroll internally rather than clip.pointer-events: noneon the card so it never intercepts events from rows it floats over. Slot dot colors read ComfyUI's runtime palette (app.canvas.default_connection_color_byType, populated at canvas init) first, then fall back to LiteGraph's staticLGraphCanvas.link_type_colors(mostly empty in stock LiteGraph), then a neutral grey. Widget classification mirrors ComfyUI's frontend rule: scalars (INT/FLOAT/STRING/BOOLEAN) and arrays-of-choices (COMBO) become widgets, everything else is a connection slot.INPUT_TYPES()is read defensively (some custom-node packs throw under unusual conditions); when one does throw, the failure is logged viaconsole.warnso a broken pack is visible during debugging. Picks whose pack isn't loaded show a "Pack not loaded" stub card pointing at the Tools-row install button. The hover card additionally tears down onvisibilitychange(page hidden) andwindow.blurso it doesn't leak across tab switches or focus loss. New moduleweb/sidebar/node_preview.jsholds the preview engine;attachHoverPreview(row, type)is wired insidemakeNodeLeafRowso every leaf-emit site (repo tree, category tree, search-flat) gets the preview for free. The renderer callsteardownPreview()before eachtreeEl.innerHTML = ""— without it, an active card whose anchor row gets detached during a re-render would leak (thepointer-events: nonecard can't be dismissed by clicking it). (#73)
Changed
- Hover preview rebuilt to read like a real node mock. The original
layout (HSL-hashed title strip + two-column slot table + flat widget
text list) read as a generic info card. Reworked to mirror upstream
NodePreview.vuemore faithfully: a flat header row (small colored dot + node display name) over a red PREVIEW badge, slot rows laid out as a 5-column grid ([dot] [input-name] [spacer] [output-name] [dot]) so inputs and outputs line up horizontally as parallel sockets, and widgets rendered as individual rounded pills with◀ name [spacer] value ▶chrome to mimic LiteGraph widget UI. Bigger fix underneath:readSlotsand the title / category / description reads were looking at staticnodeClass.INPUT_TYPES/RETURN_TYPESproperties, which don't exist on ComfyUI-registered nodes — ComfyUI parks the original V1 def atnodeClass.nodeData(set bylitegraphService.ts'sregisterNodeDefright beforeLiteGraph.registerNodeType). Every card was rendering as just-the-header-and-badge because the slot arrays came back empty. Reading fromnodeDatafirst (with the old property names as a legacy fallback for non-ComfyUI registered nodes) fixes the empty-card class. (#78)
Fixed
- Module save/insert now preserves internal links more defensively.
Selection save and module insert both accept serialized link arrays and
object-shaped LiteGraph
LLinkrecords, compare node/link ids by stable string keys, and strip stale saved link ids before recreating links vianode.connect(...). Saved module state is now first-class (module: true) while still honoring the legacymoduletag, so left-click module insert and right-click Tags-based toggling stay in sync. pinExpandedpaths now expand on the immediate render instead of being delayed by one.buildFolder's expansion-resolution chain gained apinnedPathscheck betweenforceExpandedandpathStates. Phase 3 ofrenderTreewrites pins intopathStatesAFTER Phase 2 has built the DOM, so without the new check the pin had no effect on the current render — only on the next one. Side-effect-free correctness improvement for the workflow-save pinning that already existed; load-bearing for the new spotlight feature above which depends on pinned paths expanding immediately.- Modal drag-out-of-input no longer dismisses the dialog. The shared
modal shell (
makeModalShell) used by every Koolook dialog gained a mousedown / click-intent check: a click on the overlay now only fires the dismiss when the gesture started on the overlay too. Drag- selecting text inside an input field that releases in the overlay's dark area used to triggerclickwithtarget=overlayand dismiss the modal mid-edit; now it stays open. Affects all dialogs including Snapshot Save / Load / Settings, Install Missing, Save Workflow, Tags, Confirm, Input — load-bearing especially for the Snapshot Settings path field where the long absolute path encourages drag-selection. - Snapshot Settings path field shows full path on hover. Added a
titleattribute synced to the saved library path on every refresh and save, so the user can read the full path without scrolling / selecting inside the narrow input.
Added (continued)
- "Install missing for picks" toolbar button in the Nodes section
(
pi-cloud-downloadicon, next to Add and Export). Walks the user's picks against ComfyUI-Manager's/customnode/getmappingsmapping, buckets into already-installed / will-install / unresolved, queues unique git URLs through/customnode/install/git_url, polls/manager/queue/statusto drive a progress bar, and prompts to reboot. Works on any install with ComfyUI-Manager loaded; falls back to a clipboard URL list (with acomfy node installhint) if Manager isn't reachable. New moduleweb/sidebar/installer.jsholds the Manager-API client + resolver; the modal inweb/sidebar/modals.jsdrives the four-phase UI (discovery → confirm → progress → result). 403s from Manager's security gate are surfaced as actionable language ("your security level forbids git-URL installs") rather than raw HTTP. - Snapshot library — save your full Kforge Labs state (curated picks
- the entire workflows store including tags + archive) as a named preset to a configurable filesystem path. New top-level Snapshot action row above the search field with three icon buttons, each opening its own focused dialog:
- Save (cloud-up icon) — one click. If a preset is currently
loaded, prompts "Save over ''?" with three options
(Save overwrite / Save as new… rename / Cancel). If
no preset is loaded yet, prompts for a name (default
preset YYYY-MM-DD, fully editable). The "current preset" is tracked in localStorage and persists across reloads, so Save keeps doing the obvious thing across sessions. - Load (cloud-down icon) — single dialog. Lists every preset in the library with metadata (workflow count · pick count · export date). Click a row → confirm "Replace current state?" → restores picks + workflows. Each row has an × button for delete (with confirm). Header line shows the current library path so you can see where you're loading from.
- Settings (cog icon) — single field for the library's filesystem path. Save writes to a per-install settings file the server reads. Reset to default clears the saved path so the server falls back to env-var or built-in default. A read-only line shows the currently-resolved path + source (settings panel / env var / built-in default), so what's in effect right now is always visible.
- Configurable storage location. Resolution chain (highest first):
- Path saved via the Settings dialog
(
<comfyui-userdata>/koolook-settings.json'slibraryPath). KFORGELABS_PRESETSenv var (deployment / facility config).- Built-in default
<comfyui-userdata>/koolook-presets/. Use cases:
- Personal cross-machine sync: point at a Dropbox/iCloud/Drive folder; the library follows your machines.
- Facility shared library: point at an NFS/SMB mount writable by every workstation; all workstations save to + load from the same library natively, no symlinks.
- Read-only distribution: point at a path the workstation can
read but not write; save fails cleanly with the server reason in
a toast, load works.
Snapshot files carry a
kind: "koolook-snapshot"discriminator +versionfield for future schema migrations. Closes the "save to a custom location, take it elsewhere" piece of #46.
- Path saved via the Settings dialog
(
Internal
- New server-side module
koolook_routes.pyregisters the/koolook/presets/*aiohttp routes on ComfyUI's PromptServer. Endpoints:info,list,file(GET/POST/DELETE on a single query-string-keyed endpoint, with the GET handler short-circuiting HEAD requests via aiohttp's auto-HEAD-from-GET so existence checks don't read the full file body), andsettings(GET/POST). The HEAD short-circuit lives inside the GET handler rather than as a dedicated@routes.headregistration because ComfyUI's mirror-to-/apicode inserver.pyblindly forwardsRouteDef.kwargsinto aRouteTableDef.route(...)closure that rejectsallow_head=False, so any kwarg-based opt-out crashes startup. Path-traversal protection at the route boundary via a strict filename whitelist regex; symlink protection via post-resolveis_relative_tocheck on every file op. Settings file is written atomically (tmp + os.replace) so an interrupted process can't truncate the user's saved library path. The library directory is auto-created on first save. replaceAllWorkflowsnow uses the samesnapshotCacherollback primitive aspersistMutationso a snapshot apply that fails to persist rolls the in-memory cache back to its pre-call state — the load is fully atomic. Theworkflows_store.jsmutator-invariants doc block listsreplaceAllWorkflowsas the fourth legitimate rebind site.- The Load dialog now gates the
currentPresetNametracker onpicksOk && workflowsOk. Partial-failure paths clear the tracker so the next Save forces a fresh name prompt rather than offering to overwrite the on-disk preset with corrupted half-state. - The Load dialog clears the tracker if the user deletes the currently-loaded preset.
- Client-side
sanitizeNamenow mirrors the server's filename whitelist regex — invalid characters collapse to_rather than hitting an opaque HTTP 400 from the server. ThepresetExistsprobe is now tri-state (true/false/null); the Save flow refuses to write when it can't reach the library to verify the name. - Server error reasons are surfaced via
await resp.text()rather thanresp.statusText, since HTTP/2 (RFC 7540) strips reason phrases — behind any HTTP/2-terminating proxystatusTextis empty and the server's helpful "read-only mount" / "invalid filename" / "parent missing" messages would otherwise be lost.
Changed
- Save selection toast distinguishes "no selection" from "selection
points at deleted nodes." Previously both produced the generic
"Select one or more nodes on the canvas first." Now the deleted-node
case (selection set non-empty but every id refers to a removed node,
common after undo/redo) shows "Selected node(s) no longer exist.
Click a node on the canvas to re-select."
serializeSelectionreturns a discriminated{ kind: "empty" | "stale" | "ok", graph? }result so the caller can route messaging precisely. curated_defaults.jsonretired in favor ofstarter_preset.json. Fresh installs no longer get picks seeded directly into localStorage on first load. Instead the bundledweb/starter_preset.json(full snapshot format — picks + workflows + tags + archive) is copied into the user's snapshot library directory asstarter.json, and the user opens Snapshot → Load to apply it in one click. The change unifies fresh-install distribution with the per-user snapshot library (#68): the maintainer flow becomes "build state in ComfyUI → click the ↓ Tools-row button → paste overweb/starter_preset.json→ commit." Existing users with non-empty picks are explicitly skipped by the new seeder, so their state is untouched. Removed:seedDefaultsIfNeeded,exportPicks,web/curated_defaults.json,SEEDED_KEY,DEFAULTS_URL. Added:seedStarterPresetIfNeeded,exportStarterPreset(both inweb/sidebar/snapshot.js),STARTER_SEEDED_KEY,STARTER_URL,STARTER_PRESET_FILENAME,web/starter_preset.json.- Sidebar toolbar: Tools row split out from Nodes row. The Export
button (now "Export starter preset") and the "Install missing for
picks" button moved up out of the Nodes row into a new dedicated
Tools row above the search field, alongside a new "Drop
placeholders onto canvas" button. The Nodes row keeps only the
everyday
+Add button. Reasoning: Export and Install-missing are admin/advanced operations; segregating them keeps the daily flow uncluttered and makes it harder to fire them by accident. The new Drop-placeholders button is thesecurity_level=normalescape hatch for "install missing" — it instantiates one placeholder per missing pack on a fresh canvas tab so ComfyUI/Manager's standard "Install Missing Custom Nodes" detection picks them up and routes the install through Manager's UI flow (which doesn't go through/customnode/install/git_url's security gate the way our programmatic call does).
Internal (sidebar tidiness pass — no behavior change)
- Lifted save-modal action sentinels and the cascade picker
sentinels into named constants at the top of
showSaveWorkflowModalinweb/sidebar/modals.js(ACTION_NEW,ACTION_USE_EXISTING,ACTION_MODIFY_EXISTING, alongside the existingNEW_TOP/SAVE_HERE). Magic strings ("new","use_existing","modify_existing") are gone from the function body. - Section-id constants (
SECTION_ID_NODES,SECTION_ID_WORKFLOWS,SECTION_ID_TAGS) lifted to module scope inweb/sidebar/tree.js. ThepinExpandedsave-flow callsite now references the constant instead of the literal"workflows"string, so a future section-id rename only needs the constant updated. modalLabel(text)factory inweb/sidebar/modals.jsreplaces the seven repetitions of the four-linecreateElement("label")+className+textContent+appendChildpattern.USERDATA_OVERWRITE_QUERY = "?overwrite=true"named constant inweb/sidebar/workflows_store.js. ComfyUI's userdata API requires this flag to allow POST over an existing file; pinning it as a constant makes the contract visible at a glance.buildFolderno longer round-trips folder-expansion state throughwrapper.dataset.expanded. The DOM dataset forces every value to a string (and led to the slightly awkward!== "false"read); expansion state now lives in a closure variable.pathStatesis the canonical store for cross-render persistence — the dataset was just mirroring it for reads.subcategoryForfallback path now logs aconsole.warnwith the offending category andcategoryRoot. Reachable only via a logic error (typically a staleREPOSentry whosecategoryRootno longer matches the upstream node category prefix); previously it silently rewrote the category, masking the misconfig.- Save-modal
onSavecallback contract renameddir→dirPathto match the codebase-wide convention (dirPathfor arrays of segments,dirNamefor single segments,dirfor resolved DirNode objects). Caller updates intree.jspaired. makeToolbarButton({iconClass, title, onClick})factory inweb/sidebar/tree.jsreplaces the four near-identical button-construction blocks inrenderPanel(export, new-dir, save-canvas, save-selection). Each block was 5–7 lines ofcreateElement/className/innerHTML/title/addEventListener; calls collapse to 4-line factory invocations. No behavior change. Closes the "extractmakeToolbarButton" item on #47.makeToolbarButton({iconClass, title, onClick})factory inweb/sidebar/tree.jsreplaces the four near-identical button-construction blocks inrenderPanel(export, new-dir, save-canvas, save-selection). Each block was 5–7 lines ofcreateElement/className/innerHTML/title/addEventListener; calls collapse to 4-line factory invocations. No behavior change. Closes the "extractmakeToolbarButton" item on #47.- Dropped duplicate
.koolook-export-btnCSS class. It was identical to.koolook-icon-btnminus the:disabledstate. The export button now uses only.koolook-icon-btnviamakeToolbarButton. Closes the "drop duplicate CSS class" item on #47. buildFolder({path})default removed.pathis now a required parameter; the previouspath = nulldefault was unreachable since every caller routes throughmakeSectionCtx, which always supplies a non-empty section-prefixed string. The runtimeif (path && …)guard insidebuildFolderis also dropped. Closes the "remove unreachable default" item on #47.workflowsCachemutator invariants documented inline above the public mutator block inweb/sidebar/workflows_store.js. Four rules a future contributor needs before adding a new mutator: pair every mutate with a commit (or usepersistMutation), returnfalsefor no-op vs. truthy for success, mutate in place, and never replaceworkflowsCacheoutside the seed/load paths. Closes the "documentworkflowsCacheinvariants" item on #47.
Added
- Right-click "Duplicate…" on any workflow row in the sidebar tree.
Opens a name modal pre-filled with
<name> (copy); saving deep-clones the source graph into a new entry in the same directory. The duplicate inherits the source's tags so the user's categorization carries over. Same-name duplicates fall through to the existing archive-on-collision behavior insaveWorkflowEntry. (Closes #58.) - Right-click "Tags…" on any workflow row. Opens a chip-style modal
to view, add, and remove the workflow's tags one at a time. Each
edit fires its own
persistMutationso changes survive a mid-edit close. (Part of #56.) - Tags sidebar section. A new section between Workflows lists every tag in use across the active workflow tree. Each tag becomes a folder whose entries are the tagged workflows (sorted A→Z); click loads the workflow from its real directory. Archived workflows are filtered out of the section so the active view stays clean — their tags are still preserved on the entry, so a restore from the Archive folder brings them back. Search matches tag name OR workflow name. (Part of #56.)
- Right-click "+ New directory…" / "+ New subdirectory under …" in the workflow row's Move-to flow. Both create a fresh directory and move the workflow into it as one atomic mutation; if the move can't land, the new directory is rolled back so the cache never leaks an empty orphan. (Closes #57.)
- Recursive subdirectories under workflow directories. Right-click any
directory in the Workflows tree → "Create subdirectory…" to nest folders
to arbitrary depth (e.g.
UP-scale / Type-A / Sharp). Each nested directory behaves like a top-level one: it can hold workflows + an Archive subfolder + further subdirectories. The save modal directory picker is cascading (multi-step): pick a parent, then a child appears, then a grandchild, etc. — each child level has a(save in "<path>")option to stop drilling at that depth. The right-click workflow "Move to…" submenu lists every other path in the tree. - Delete archive (N) on the synthetic Archive folder. Right-click the Archive folder under any directory → bulk-removes every archived workflow at that level in one confirm. Active workflows in the same directory are untouched.
- Drag-and-drop in the workflows tree (Tier 1 — moves only, no reordering). Drag a workflow onto a directory to move it. Drag a workflow onto an Archive folder to archive it (cross-directory drops move + archive in one go). Drag a directory onto another directory to nest it as a child. Cycle prevention rejects dropping a dir into itself or any of its descendants. Sort within a level stays alphabetical. (Custom ordering would be Tier 2.)
- Schema is now recursive — every directory node has a
workflowsobject AND adirectoriesobject. Existing v0.2 stores load fine:normalizeWorkflowsStoretreats a missingdirectoriesas{}and the rest of the code assumes it always exists post-normalization.
Changed
- Sidebar tab renamed: "Curated Nodes" → "Kforge Labs". Tooltip
also updated. The tab id (
koolook.curatedNodes) and theapp.registerExtensionname are unchanged so existing per-user tab state (pinning, ordering) is preserved. - Save modal —
Base on existingcandidates now walk leaf-up to root and dedupe (deepest wins). Saving into an empty subdirectory likeUP-scale / seedvr2no longer disables the existing-name actions — the modal pulls active workflows from every ancestor. Ancestor entries are labeled<name> · in <path>so the candidate's source directory is unambiguous. The selected destination path is whatever the cascade resolves to — the ancestor source only seeds the workflow name; archive semantics still apply at the destination. - Save modal —
Actiondropdown is now hidden (not just disabled) when no base candidate exists anywhere in the destination's ancestry. Disabled<option>elements render too subtly across browsers; the only useful path in that case is "type a fresh name and save," which the Workflow Name field below already provides. The underlying value is pinned tonewso submit takes the by-name path. - The right-click canvas-node menu item is now "Add to Kforge Labs" (was "Add to Curated Sidebar").
- Directory header counts in the workflows tree now show the total workflows in the whole subtree (active + archived + descendants), not just direct children. A parent with empty direct workflows but populated subdirectories no longer shows "0".
Internal
- Workflow operations are now path-addressed: every mutator and lookup
takes a
string[]path (e.g.["UP-scale", "Type-A"]). The store's internal API:addDirectory(parentPath, name),renameDirectory(parentPath, old, new),deleteDirectory(parentPath, name),saveWorkflowEntry(path, wfName, graph),archive/unarchive/rename/deleteWorkflow(path, wfName),moveWorkflow(srcPath, wfName, dstPath), plus new helperslistAllDirectoryPaths()andpathsEqual(a, b). - Reserved-name check: subdirectory names cannot be
Archive(case-insensitive) at any non-root level — collides with the synthetic Archive folder rendered for archived workflows. - Workflow entries gain optional
tags: string[].normalizeDirNodetrims, drops empties, and dedupes case-sensitively, so old entries without a tags field load astags: []and the rest of the code can assume the field always exists. New mutators inworkflows_store.js:getWorkflowTags(path, wfName),addTag(path, wfName, tag),removeTag(path, wfName, tag).getWorkflowGraph(path, wfName)is now also exported so the duplicate flow can deep-clone without reaching into the cache directly. showTagsModalinmodals.js— chip-row UI with add/remove callbacks, mirrors the existingshowInputModal/showConfirmModalsurface so the modal shell, escape-key teardown, and overlay-click dismissal stay centralized.
Documentation
- Closed out issue #28 (de-vendor upstream code under
upscaler_FIX/andnuke_CAM_exporter/) by adding the audit-trail layer that the v0.1.4 / v0.1.5 registry-hygiene cleanup deferred:forks/THIRD_PARTY.mdgained a "De-vendored upstream code" section listing all six untracked trees with former path, upstream (where pinned) and per-tree provenance notes. Preamble now also documents the publish-history finding that none of these files were ever in a successful Comfy Registry publish.forks/forks_manifest.yamlgained six entries with the_devendoredsuffix andstatus: "removed", populatingsource_repo/source_ref/local_paths/removed_in_release/licenseper issue #28's acceptance criterion (2). Best-effort where upstream URLs were not pinned at vendor time. Closes #28.
[0.2.0] - 2026-05-04
Removed (BREAKING for any saved workflow using Easy_Version)
- Dropped
Easy_Version(and thek_easy_version.pysource) — the whole node was just a one-liner that turned an integer N into the stringvNNN(zero-padded). Maintainer concluded it was the first trivial node they ever wrote and saw no real value. Anyone who needs this exact behavior can either:- inline the format string in their workflow downstream of an INT
primitive (
f"v{N:03d}"), or - use any of the more general string-format nodes in the ecosystem (KJ Nodes, ComfyUI-Custom-Scripts, etc.).
- inline the format string in their workflow downstream of an INT
primitive (
- Saved workflows that reference the
Easy_VersionID will fail to load — same migration as v0.1.5's__koolook_v1_0_1cleanup.
Fixed (carried over from PR #39, [Unreleased] in 0.1.8)
-
Easy_hdr_VAE_encode(Koolook v2.3.3) now wraps the encoded tensor in the standard ComfyUILATENTdict ({"samples": t}) instead of returning the raw tensor. Wiring this node into KSampler previously crashed withIndexError: too many indices for tensor of dimension 5on Wan 2.2 video workflows, because KSampler doeslatent["samples"]and the raw 5-D tensor doesn't support string indexing. The decoder side was already correct. -
Easy_hdr_VAE_encode/Easy_hdr_VAE_decode(Koolook v2.3.3) now produce proper N-frame sequences end-to-end on Wan 2.2 / Hunyuan / CogVideoX / LTX video workflows. Two combined fixes:- Rank-aware dispatch. 5-D
(B, F, H, W, C)video tensors paired with a 2-D image VAE are iterated frame-by-frame and stacked along a temporal axis on encode (and per-frame decoded then concatenated to a(B*F, H, W, C)ComfyUI IMAGE batch on decode). 5-D tensors with a 3-D-native VAE — identified viavae.latent_dim == 3, which is the actual attribute ComfyUI sets on its VAE class for video VAEs — pass through unchanged. - 5-D output normalization on decode. 3-D-aware VAEs return a
5-D
(B, F, H, W, C)tensor fromvae.decode(). Without an explicit reshape, downstream IMAGE-typed nodes (Get Image Size, SaveImage) misread that as a single 4-D image withcount=1andheight=F— exactly the symptom we were debugging (1056×41 count=1after a 41-frame Wan 2.2 sequence). The fix mirrors the stock ComfyUI VAEDecode node (nodes.py:303-304):if img.ndim == 5: img = img.reshape(-1, H, W, C)— the leading(B, F, …)dims collapse into the standard ComfyUI batch axis, so SaveImage now writes_00001.png … _0004N.pnginstead of one squashed frame.
The new
path=image | video-iter | video-3dfield indebug_infosurfaces which dispatch branch fired per run, for live verification. - Rank-aware dispatch. 5-D
Added (Easy_hdr_VAE_encode / decode feature parity with upstream)
- Restored the cinema-grade color-management surface from upstream
Radiance v2.3.3's
RadianceVAE4KEncode/Decode, while keeping the slim no-tile-engine code path that lets these nodes work with Wan 2.2 video VAEs (the original motivation for the Koolook fork — upstream's 4K cosine-blend tile engine errors with"size of tensor a (192) must match the size of tensor b (132) at non-singleton dimension 4"on video VAEs because the tiler's spatial alignment fights the video VAE's internal temporal-aware encoding). - 12 source / target color spaces (was 4): Linear, ACEScg,
ACES 2065-1, Rec.2020 Linear, sRGB, Raw, plus six cinema log
curves — ARRI LogC3 (EI-aware, default 800), ARRI LogC4,
Sony S-Log3, Panasonic V-Log, DaVinci Intermediate,
RED Log3G10. Round-trip math (linearize → encode) and matrix
constants ported verbatim from upstream's
color_utils.pywith the bug-fix history preserved in the docstrings. Conversions are rank-agnostic and 4-channel-aware (alpha passes through untouched). Compress (Log)HDR mode — the upstream HDR-clamp-free pipeline. On encode, the linear-space tensor is re-encoded through a cinema log curve (matchessource_spaceif it's a log space, else ARRI LogC4) and goes into the VAE without a hard clamp. On decode, the VAE output is run through_soft_log_shoulder(tanh rolloff at the per-profile knee instead of a hard clamp at 1.0) and_denoise_log_highlights(3×3 box blur in log space, ramped quadratically toward the highlight region) before log→linear conversion. Per-profile parameters (LOG_PROFILE_HDR_PARAMStable) tune knee / ceiling / denoise threshold / strength to each curve's slope at code 1.0 — RED Log3G10 gets the most conservative settings, Sony S-Log3 / ARRI LogC4 the loosest. Pairhdr_mode="Compress (Log)"on both encode and decode for HDR-clean roundtrips; mismatched modes produce garbage by design.latent_samplingparameter (sample/mean/mode).sampleis ComfyUI's default random posterior sample.meanandmodeuse the posterior mean for deterministic, lowest-noise encoding — best for img2img where minimum reconstruction noise matters. The mean/mode path replicates ComfyUI's preprocessing (BHWC → BCHW → [-1, 1] scaling) before reachingfirst_stage_model, with a graceful fallback chain that never crashes.- Real alpha output.
Easy_hdr_VAE_encodenow returns(LATENT samples, STRING debug_info, IMAGE alpha). Withalpha_handling="Preserve"and a 4-channel input, the alpha channel is surfaced as a separate IMAGE for downstream re-compositing post-decode (VAEs don't encode alpha, so it's routed around them). Withalpha_handling="Ignore"or a 3-channel input, alpha is a zeros tensor of compatible shape — downstream wiring never fails. The previousalpha_handlingflag was a documented no-op. - Exposure now applied in linear space for all source spaces. The
previous behavior multiplied raw input bytes by
2^exposureeven when the input was sRGB-gamma or log-coded, which produced visually wrong results for non-linear sources. Linear sources are unaffected (linear × 2^stops is the correct semantic). Raw source still does a raw-domain multiplication so users who know what they're feeding the VAE keep their bytes intact. - New helper module
forks/radiance_koolook/versions/v2_3_3/color_helpers.pycontains all log curves, soft-shoulder / log-denoise helpers,encode_with_sampling_mode, color-space matrices, and dispatch tables.nodes_vae.pykeeps the node wiring + sequence dispatch.
Intentionally NOT ported (upstream-only features)
- 4K cosine-blend tile engine and its
tile_size/overlap/processing_modeknobs — this is the broken plumbing that motivated the slim fork in the first place. inverse_tonemap/target_stops(SDR→HDR expansion)..rhdrsidecar export andrhdr_precision(Radiance Viewer-specific).crop_padding(only relevant when tiling).
These can be added back in future patches if needed; the slim wrapper
stays at ~600 lines of node wiring + ~450 lines of color helpers,
~40% the size of upstream's vae.py (2,638 lines).
Added (carried over from PR #40, also unreleased on main)
scripts/sync_to_dev.py— pure-stdlib helper that copies the curated runtime files (__init__.py,config.json, top-levelk_*.py,forks/,web/) into a live ComfyUIcustom_nodes/<pack>/folder for fast local iteration without bumping a version. Reads the target path from the newKOLOOK_COMFYUI_DEV_PATHenv var (auto-loads.envfrom repo root); errors out cleanly when unset..env.example—KOLOOK_COMFYUI_DEV_PATH=entry with comment.CLAUDE.md— new "dev-sync" trigger-phrase section so the agent knows to runscripts/sync_to_dev.pywhen the maintainer says "dev-sync" / "sync dev" / "copy those files" mid-session.
Net effect
- Node count drops from 8 to 7.
Koolook/Pipelinesubfolder now has only 1 entry (Easy AI Pipeline) instead of 2.- Faster local dev loop: a one-line tweak no longer requires cutting a full release just to test it inside ComfyUI.
[0.1.8] - 2026-05-03
Changed (categories — affects ComfyUI node-add menu)
Koolook/VFXis gone. Three more granular subfolders replace it:Koolook/Pipeline—Easy_Version,EasyAIPipeline(workflow setup nodes)Koolook/Image—EasyResize_Koolook,easy_ImageBatch(joinsEasyResize_Koolook;easy_ImageBatchwas previously in VFX)Koolook/VAE—Easy_hdr_VAE_encode,Easy_hdr_VAE_decode(now have their own subfolder; previously also in VFX)
- Workflows continue to load and run unchanged —
CATEGORYis purely a UI-organization hint and only affects the node-add menu hierarchy.
Fixed (search discoverability)
Easy_hdr_VAE_encode/Easy_hdr_VAE_decodedisplay names are now "Easy HDR VAE Encode (Koolook)" / "Easy HDR VAE Decode (Koolook)". Previously the display name equalled the node ID (Easy_hdr_VAE_encodewith no "Koolook" string), so typingkoolookin ComfyUI's node-add search filter excluded them — they appeared registered but invisible. Now they group with the rest of the pack under that filter.- Class IDs (
Easy_hdr_VAE_encode,Easy_hdr_VAE_decode) are unchanged — saved workflows that reference these IDs continue to load.
[0.1.7] - 2026-05-03
Changed
- All node display names now suffix
(Koolook)so they surface together when users search "koolook" in the ComfyUI node-add menu. Previously onlyKoolookLoadCameraPosesAbsolute(and the v2.3.3 fork nodes via their version suffix) matched.Easy_Version→Easy Version (Koolook)EasyAIPipeline→Easy AI Pipeline (Koolook)easy_ImageBatch→Easy Image Batch (Koolook)EasyWan22Prompt→Wan 2.2 Easy Prompt (Koolook)Easy_Versioncategory moved fromVFX/UtilstoKoolook/VFXto match the rest of the pack.
- Class keys (
NODE_CLASS_MAPPINGS) are unchanged — saved workflows keep loading and running unchanged. - Carried in via PR #36, then bundled into this release alongside the documentation reorg below.
Docs reorg (root cleanup)
- Moved out of repo root (preserving git history via
git mv):Glossary.md→docs/reference/glossary.mdRELEASING.md→docs/maintainers/releasing.md
- Root now keeps only the four files that must live there:
README.md(GitHub repo home + Comfy Registry description),LICENSE(pyproject.tomlreference + GitHub license badge),CHANGELOG.md(tooling convention),CLAUDE.md(Claude Code agent instructions). - New
docs/structure with three audience buckets:docs/user_guide/— end-user, per-node guides + screenshots (img/)docs/reference/— lookup material (glossary, node inventory)docs/maintainers/— project-internal procedures
- Each bucket gets a short index
README.mdso the structure is navigable fromdocs/README.mddownwards without grep.
Added (new maintainer docs)
docs/maintainers/registry-api.md— documents the undocumented Comfy Registry version-management API that we reverse-engineered while cleaning up v0.1.0–0.1.5 (deprecate / undeprecate / yank endpoints, status enum values, auto-deprecation behavior, ready-to-paste curl recipes, and an optionalregistry-mgmt.ymlGitHub Actions workflow for codified version management without ever exposing the token to a shell).docs/maintainers/node-versioning.md— codifies the rules from the v0.1.5 PR-review discussion for safely changing a node'sINPUT_TYPES/RETURN_TYPES/ class names without breaking saved user workflows. Five rules + the suffix-version pattern- the alias-then-deprecate migration path + concrete worked examples.
- Cross-references in
README.md,CLAUDE.md,.github/ISSUE_TEMPLATE/release_checklist.md, and theadd-external-forkskill all updated to point at the new paths. - Imported the existing untracked
docs/images that the maintainer had already started adding locally (node inventory screenshot underreference/, Easy Image Batch helper image underuser_guide/img/).
[0.1.6] - 2026-05-03
Renamed (with back-compat alias)
EasyResizeis now exposed canonically asEasyResize_Koolook(display:Easy Resize (Koolook)) to resolve a node-ID collision withComfyUI-EasyFilePaths, which also registers the bare nameEasyResize. The oldEasyResizeID is kept as a deprecated alias pointing at the same class, so saved workflows still load and run unchanged. The alias will be removed in a future major release once the deprecation has had time to propagate.
Attribution
- Added a proper SPDX header + GPL-3.0 attribution block to
k_easy_resize.py, creditingkijai/ComfyUI-KJNodes(Resize Image V2) as the original interface inspiration. The Koolook implementation is a fresh write that materially extended the surface (aspect-ratio parser, keep_proportion modes, mask + composed outputs, device selection, target/original W/H reporting, color-panel passthrough). KJ Nodes is GPL-3.0, same as our pack — no relicense required. forks/THIRD_PARTY.mdandforks/forks_manifest.yamlupgraded the KJ Nodes entry fromlicense: "unknown"/sync_state: "needs-upstream-reference"to verified GPL-3.0 with full per-feature change notes.
Notes for users
- If a saved workflow uses the bare
EasyResizeID, it still works but the node's display name now readsEasy Resize (deprecated, use 'Easy Resize (Koolook)')as a hint to swap. New workflows should pickEasyResize_Koolookfrom the node-add menu.
[0.1.5] - 2026-05-03
Removed (BREAKING for anyone using the v1_0_1 namespaced IDs)
- Dropped the entire
forks/radiance_koolook/versions/v1_0_1/folder (~5,200 lines, 26 namespaced nodes). The wrappers were vestigial — Koolook authors never used them, no internal workflow referenced any__koolook_v1_0_1suffixed ID, and the VAE pair was already superseded byEasy_hdr_VAE_encode/Easy_hdr_VAE_decodein v2_3_3. - IDs that no longer exist after this release:
ImageToFloat32__koolook_v1_0_1,Float32ColorCorrect__koolook_v1_0_1,HDRExpandDynamicRange__koolook_v1_0_1,HDRToneMap__koolook_v1_0_1,ColorSpaceConvert__koolook_v1_0_1,SaveImageEXR__koolook_v1_0_1,LoadImageEXR__koolook_v1_0_1,LoadImageEXRSequence__koolook_v1_0_1,SaveImage16bit__koolook_v1_0_1,HDRHistogram__koolook_v1_0_1,LogCurveEncode__koolook_v1_0_1,LogCurveDecode__koolook_v1_0_1,HDRExposureBlend__koolook_v1_0_1,HDRShadowHighlightRecovery__koolook_v1_0_1,OCIOColorTransform__koolook_v1_0_1,OCIOListColorspaces__koolook_v1_0_1,GPUTensorOps__koolook_v1_0_1,HDR360Generate__koolook_v1_0_1,SaveHDRI__koolook_v1_0_1,ACES2OutputTransform__koolook_v1_0_1,DaVinciWideGamut__koolook_v1_0_1,ARRIWideGamut4__koolook_v1_0_1,RadianceVAEEncode__koolook_v1_0_1,RadianceVAEDecode__koolook_v1_0_1,k_easy_OCIO_v101(the short ID forRadianceOCIOColorTransformV2),RadianceLogCurveDecode__koolook_v1_0_1. - Migration path: install upstream Radiance directly
(https://github.com/fxtdstudios/radiance) for the HDR/EXR/OCIO
functionality. Use
Easy_hdr_VAE_encode/decode(already in v0.1.3+) for video VAE workflows. - Source recoverable via
git checkout HEAD~1 -- forks/radiance_koolook/versions/v1_0_1/if you ever realize you need any of those wrappers back.
Added (registry hygiene from the original v0.1.4 plan)
.gitignorenow excludesupscaler_FIX/andnuke_CAM_exporter/— the maintainer's local dev workspaces accidentally committed in Dec 2025. These were never imported by the package's root__init__.py, so they had no runtime effect, but the Comfy Registry's static scanner picked upNODE_CLASS_MAPPINGSfrom vendored 3rd-party clones inside them and counted ~12 spurious nodes against this pack in ComfyUI-Manager (yielding the misleading "44 nodes / 13 conflicts" badge).
Removed (registry hygiene)
git rm -r --cached upscaler_FIX nuke_CAM_exporter— 70 files untracked from git, files stay on the maintainer's local disk for reference. ~3.7 MB of unrelated content out of the published archive.
Net effect on Manager / registry
- Node count drops from 44 to 8 (6 root Koolook + 2 v2_3_3 VAE).
- Spurious "Conflict with
ComfyUI-SuperUltimateVaceTools" warnings disappear. - Published archive shrinks by ~9 MB total (3.7 MB dev workspaces + ~5.2 MB v1_0_1 fork code).
Notes for the maintainer
- After merging this PR and
git pull-ing main, your local working tree will lose theupscaler_FIX/andnuke_CAM_exporter/folders (git applies the deletion). Back up first or restore viagit checkout HEAD~1 -- .... Theupscaler_FIX/folder has already been physically moved to../ComfyUI-Forks-BK/; the_Utils-CAM-track/subfolder ofnuke_CAM_exporter/has been moved to../ComfyUI-Tools-BK/nuke_CAM_exporter/. The remainder ofnuke_CAM_exporter/(your actual Nuke pipeline work) is still on disk in MAIN but no longer tracked by git.
[0.1.4] - 2026-05-03 (test-published only, superseded by 0.1.5)
Removed (registry hygiene)
- Untracked the maintainer's local dev workspaces from git:
upscaler_FIX/andnuke_CAM_exporter/. These were never imported by the package's root__init__.py, so they had no effect on what ComfyUI loaded at runtime, but the Comfy Registry's static scanner picked up theNODE_CLASS_MAPPINGSdicts inside vendored 3rd-party clones and counted them as part of this pack — yielding a misleading "44 nodes / 13 conflicts" badge in ComfyUI-Manager (againstComfyUI-SuperUltimateVaceTools,ComfyUI-multigpu, etc.). Files removed from index viagit rm -r --cached(still on the maintainer's local disk) and the two paths are now.gitignored so they cannot leak again. - Net effect: published archive shrinks by ~3.7 MB (70 files), Manager's node count drops from 44 to ~32 (the actual runtime registrations: 6 root Koolook + ~26 namespaced fork variants), and the spurious conflict warnings disappear. Same class of issue as the v0.1.2 GPL-3.0 relicense — vendored 3rd-party code in MAIN, this time without the license-compatibility risk because none of it was ever imported.
Notes for the maintainer
- After merging this PR and
git pull-ing main, your local working tree will lose theupscaler_FIX/andnuke_CAM_exporter/folders (git applies the deletion). If you want to keep working on those locally, back them up first (cp -r upscaler_FIX nuke_CAM_exporter ~/backup/) or restore from history afterwards (git checkout HEAD~1 -- upscaler_FIX nuke_CAM_exporter); they will then live as untracked files in your working tree, ignored by the new.gitignorerules.
[0.1.3] - 2026-05-03
Renamed
- The new v2_3_3 VAE nodes are now exposed as
Easy_hdr_VAE_encode/Easy_hdr_VAE_decode(clean IDs and display names — no__koolook_v2_3_3suffix), to avoid visual collision with upstream Radiance v2.3.3'sRadianceVAEEncode/RadianceVAEDecodealiases in the ComfyUI node-add search. The version is still tracked structurally (file lives inversions/v2_3_3/) and textually (file header + UPSTREAM_PIN.yaml + forks/THIRD_PARTY.md). Other Koolook nodes in the v2_3_3 set (none today, but possible in future) would still use the__koolook_v2_3_3suffix by default — opt out via the newSKIP_VERSION_SUFFIXset inversions/v2_3_3/__init__.py. - 0.1.2 was test-published to the registry with the previous
RadianceVAEEncode__koolook_v2_3_3IDs but never tagged or formally released; bumping to 0.1.3 publishes the renamed version cleanly.
[0.1.2] - 2026-05-03 (test-published only, superseded by 0.1.3)
License (BREAKING)
- Relicensed entire package to GPL-3.0. v0.1.0 and v0.1.1 shipped under
a claimed MIT license while already incorporating GPL-3.0-derived code from
fxtdstudios/radiance under
forks/radiance_koolook/. GPL-3.0 §5(c) requires the entire combined work to be GPL-3.0; relicensing aligns the package with what we actually ship and matches the dominant license posture of the ComfyUI custom-node ecosystem. Downstream users incorporating, linking to, or deriving from ComfyUI-Koolook must now distribute under GPL-3.0 (or compatible). - Added
LICENSEfile at repo root with the full GPL-3.0 text (pyproject.tomlpreviously referenced aLICENSEfile that did not exist on disk). - README "License" section rewritten to declare GPL-3.0 and explain the §5(c) implication.
Added
forks/radiance_koolook/versions/v2_3_3/— slim, video-friendly re-implementation ofRadianceVAEEncode/RadianceVAEDecodeexposed under the namespace suffix__koolook_v2_3_3. Mirrors the interface surface of upstreamRadianceVAE4KEncode/RadianceVAE4KDecodebut skips the 4K cosine-blend tile engine, which conflicts with modern video VAEs (Wan 2.2, Hunyuan, CogVideoX, LTX) that already handle their own temporal/spatial stitching internally. Fixes the"size of tensor a (192) must match the size of tensor b (132) at non-singleton dimension 4"runtime error users hit when chaining upstream's VAE encoder into Wan 2.2 video workflows.forks/THIRD_PARTY.md— full attribution entries for the v1.0.1 baseline (already in the repo) and the new v2.3.3 VAE subset, with per-class change notes..claude/skills/license-pre-check/— blocking license-compatibility audit skill for Claude Code. Run before copying or porting any third-party code; refuses to proceed on incompatible combinations (e.g. GPL upstream into MIT downstream)..claude/skills/add-external-fork/— Claude Code port of the existing Cursor skill, with a mandatory Phase 0 ("run license-pre-check first") and a Phase 3c requirement to add GPL §5(a) modification headers on derived files.RELEASING.md— canonical, step-by-step release procedure (was previously ad-hoc; the gaps it closes are exactly what caused thev0.1.0orphan-tag andCristianPPublisherIdincidents).- README "Release & Stability" section now links
RELEASING.mdand the per-release checklist template. CLAUDE.mdnow referencesRELEASING.mdfor agents.
Changed
forks/forks_manifest.yaml— explicitlicense: "GPL-3.0"andlicense_verified_atfields on the existing radiance entry (wasto_verify_at_source_ref); pre-registered the new v2.3.3 VAE entry with verified license metadata..github/ISSUE_TEMPLATE/release_checklist.mdrewritten to mirrorRELEASING.md, including the registry-publisher validation step.
Notes for users
- If your workflow currently references the bare-name
RadianceVAEEncodeorRadianceVAEDecode(which routes to upstream Radiance v2.3.3'sRadianceVAE4KEncode/Decodevia that package's alias), and you hit the 4D vs 5D tensor mismatch on video workflows, switch to the namespacedRadianceVAEEncode__koolook_v2_3_3/RadianceVAEDecode__koolook_v2_3_3(display name suffix (Koolook v2.3.3)). - The existing
__koolook_v1_0_1namespaced nodes are unchanged and remain available for backward compatibility with saved workflows.
[0.1.1] - 2026-05-03
Fixed
- Re-tag release at current
mainHEAD sogit describe --tagsresolves locally (the originalv0.1.0tag points at an orphaned merge commit that is no longer inmain's ancestry, which caused ComfyUI-Manager to display the installed version as "unknown"). - Correct
[tool.comfy] PublisherIdfrom the placeholderCristianPto the real publisherkforgelabs, which was the actual cause of everyPublish to Comfy registryworkflow failure (registry returned a misleading400 "Failed to validate token"because the declared publisher did not exist).
Changed
- Bumped
pyproject.tomlversionto0.1.1to match the new tag.
Chore
.gitignore: fix syntax and exclude__pycache__/,*.pyo,.DS_Store(carried forward from commit8fc28d1).
[0.1.0] - 2026-04-24
Added
- Fork tracking moved to
forks/with centralized workflow documentation. - Versioned Radiance fork package layout under
forks/radiance_koolook/versions/v1_0_1. CLAUDE.mdandGlossary.mdto keep workflow and naming conventions explicit.
Changed
- Root node loader now imports the versioned Radiance fork entrypoint.
- Release/checklist/template references updated to current fork-based paths.
- Introduced compact node ID override for OCIO transform:
k_easy_OCIO_v101.
Removed
- Legacy
ACES_workflow/radiancetree and duplicated tracking paths underthird_party/. - Deprecated docs/assets/workflow artifacts removed during repository cleanup.