Commit Graph
145 Commits
Author SHA1 Message Date
malkuthroandClaude Opus 4.7 2f370a962c chore(sidebar): finish #47 tidiness — UX touches + magic-string lift + small refactors (#64)
Closes the remaining checkboxes on #47 (Sidebar follow-ups deferred
from PR #45). Bundles seven items in one PR per the new "default to
bundling small slices" policy: two user-visible UX improvements + five
internal hygiene fixes.

User-visible (Changed)
----------------------
- **Save selection toast differentiates "no selection" from "selection
  points at deleted nodes."** `serializeSelection` returns a
  discriminated `{ kind: "empty" | "stale" | "ok", graph? }` instead
  of plain `null` / graph. The "stale" case (selection set non-empty
  but every id refers to a removed node — common after undo/redo)
  now toasts "Selected node(s) no longer exist. Click a node on the
  canvas to re-select." instead of the misleading generic message.
- **Curated-defaults seed failure now toasts.** Previously
  console-only; users without DevTools never saw it. The most common
  cause (localStorage quota / private-mode rejection) is something
  the user can act on.

Internal (no behavior change)
-----------------------------
- Lifted save-modal action sentinels (`new` / `use_existing` /
  `modify_existing`) into `ACTION_*` named constants at the top of
  `showSaveWorkflowModal`, alongside the pre-existing `NEW_TOP` /
  `SAVE_HERE` cascade sentinels.
- Section-id constants (`SECTION_ID_NODES`, `SECTION_ID_WORKFLOWS`,
  `SECTION_ID_TAGS`) lifted to module scope in `tree.js`. `pinExpanded`
  in the save flow now references the constant instead of the literal
  `"workflows"` string, so a future section-id rename only needs the
  constant updated.
- `modalLabel(text)` factory in `modals.js` replaces 7× repetition of
  the four-line `createElement("label")` + `className` + `textContent`
  + `appendChild` pattern.
- `USERDATA_OVERWRITE_QUERY = "?overwrite=true"` named constant in
  `workflows_store.js`. ComfyUI's userdata API requires this flag to
  allow POST over an existing file; pinning the contract as a
  constant makes it visible at a glance.
- `buildFolder` no longer round-trips folder-expansion state through
  `wrapper.dataset.expanded`. State lives in a closure variable;
  `pathStates` remains the canonical cross-render store.
- `subcategoryFor` fallback path now logs `console.warn` with the
  offending category + `categoryRoot`. Reachable only via a logic
  error (stale `REPOS` config); previously silent.
- Save-modal `onSave` callback contract renamed `dir` → `dirPath` to
  match codebase-wide convention (`dirPath` for arrays of segments,
  `dirName` for single segments, `dir` for resolved DirNode objects).
  Caller updates in `tree.js` paired.

Skipped from #47 (with rationale)
---------------------------------
- The broader `el(tag, props, children)` DOM helper from the issue
  body. Adopting it consistently would touch every modal/folder/leaf
  row construction site (~40+ replacements) for marginal readability
  win. The narrower `modalLabel(text)` is the meaningful slice
  because it replaces an exact 4-line repetition. If `el()` becomes
  desirable later, file as a focused refactor PR.

Verification
------------
- `python tools/preflight_release.py --skip manager-meta -v` PASS.
- `node --input-type=module --check` on every modified JS file —
  clean.
- Synced to live ComfyUI install via `scripts/sync_to_dev.py` for
  smoke-test on Mac.

Closes #47.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 09:19:48 +02:00
malkuthroandClaude Opus 4.7 bebc6f618d feat(sidebar): right-click menu pack — Duplicate, Tags, + New directory move (#59)
* feat(sidebar): right-click menu pack — Duplicate, Tags, + New directory move

Three sibling sidebar slices bundled because they all extend the same
workflow-row right-click menu in `web/sidebar/tree.js` and share a small
schema/store surface in `workflows_store.js`. Refactor cleanup is held
back for a separate PR (issue #47) so this diff stays a "no behavior
change to existing flows" feature add.

Changes
-------
- Right-click "Duplicate…" → name modal with `<name> (copy)` default;
  deep-clones the source graph into a new entry in the same directory
  via `saveWorkflowEntry`. 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. Closes #58.
- Right-click "Tags…" → chip-row modal with add/remove. Each edit
  fires its own `persistMutation` so changes survive a mid-edit
  close. New `showTagsModal` in `modals.js` mirrors the existing
  `showInputModal` / `showConfirmModal` shape (escape-key teardown,
  overlay-click dismissal centralized in `makeModalShell`).
- New Tags sidebar section between Workflows. Flat list of tag folders;
  each tag holds the workflows carrying it (sorted A→Z). Click loads
  the workflow from its real path. Archived workflows are filtered
  out of the section so the active view stays clean — their tags
  persist on the entry, so a restore brings them back. Search matches
  tag name OR workflow name.
- Right-click "+ New directory…" / "+ New subdirectory under <path>…"
  in the 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.

Schema additions (back-compatible)
----------------------------------
- Workflow entries gain optional `tags: string[]`. `normalizeDirNode`
  trims, drops empties, and dedupes case-sensitively, so old entries
  without a tags field load as `tags: []` and the rest of the code can
  assume the field always exists. Pre-v0.3 stores load fine.
- New exports in `workflows_store.js`: `getWorkflowTags`, `addTag`,
  `removeTag`. `getWorkflowGraph` is now also exported so the duplicate
  flow can deep-clone without reaching into the cache directly.

Why one PR for three issues
---------------------------
All three slices touch `workflowRowContextMenu` in `tree.js` and the
mutator API in `workflows_store.js`. A coherent UX-feature scope ("next
batch of workflow menu enhancements") reviews more cheaply as one
diff than three near-identical PRs. Issue #47 (refactor / tidiness
pass) stays separate because mixing refactor and feature in the same
diff defeats the point of the split — that work is queued for a
follow-up PR.

Testing
-------
- `python tools/preflight_release.py --skip manager-meta -v` PASS
  (static / dispatch / workflows fixtures, all 3 stages).
- `node --input-type=module --check` passes on each modified JS file.
- Browser smoke test: pending — needs a ComfyUI session for the
  user to verify the three menu actions and the Tags section against
  a real workflow store. The CI gates are Python-only and don't catch
  JS regressions in the sidebar by themselves.

Related
-------
- Closes #57 (move-to: + New directory…)
- Closes #58 (Duplicate)
- Implements the right-click + section parts of #56 (Tags); the issue
  body's `addTag` / `removeTag` / `listAllTags()` ops landed under
  slightly different names (`addTag`, `removeTag`, `gatherTags`).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sidebar): address review findings on PR #59

Self-review pass against the validated agent findings. Five fixes total
across the three issues' surface, holding refactor/file-size concerns for
follow-up #47 widening.

Fixes
-----
- **HIGH** `normalizeDirNode` now rejects non-string `tags` elements
  outright instead of `String(raw).trim()` coercing them. `null` /
  `undefined` / `true` / `123` no longer silently surface as the literal
  tag strings `"null"` / `"undefined"` / etc. — `stats.dropped`
  increments instead so the existing console warning catches malformed
  migrations. Closes review HIGH-1.
- **HIGH** Tags modal now distinguishes "no tags yet" from "workflow
  no longer exists." The `getCurrentTags` adapter no longer collapses
  `null` to `[]`, and `renderChips` renders an explicit gone-state
  with disabled add input + descriptive placeholder. Prevents users
  typing into a doomed modal when a concurrent action (another tab,
  drag-and-drop) deletes the workflow under them. Closes review HIGH-3.
- **MEDIUM** Duplicate flow re-validates source existence at submit-time
  inside the `mutate` callback. Without it, a concurrent delete of the
  source between menu-open and submit would let `saveWorkflowEntry`'s
  `ensureDirectoryAtPath` silently re-create the parent directory and
  paste a clone of the captured graph — effectively undoing someone
  else's delete. Now returns `false` on missing source, surfacing
  "Could not duplicate — <name> no longer exists." Closes review
  MEDIUM-6.
- **MEDIUM** Duplicate flow now routes inherited tags through the
  public `addTag` mutator instead of writing `dir.workflows[newName].tags`
  directly. Keeps the tree.js UI layer outside the store's internal
  shape. The whole sequence still rides on one `persistMutation`
  snapshot — commit failure rolls back save + tag adds together.
  Closes review MEDIUM-5.
- **LOW** `gatherTags` comment block clarifies that grouping is
  case-sensitive but search filter is case-insensitive substring.
  The comment previously said "Comparison is case-sensitive" which
  was technically true for grouping but misleading on its own.

Skipped with rationale
----------------------
- **HIGH-2 modal cache desync on persist failure** — agent analysis
  was wrong on trace. `renderChips` reads from cache, X-click does
  not optimistically remove the chip from DOM, and `restore()`
  reverts cache to pre-mutation. DOM and cache stay consistent across
  every commit-failure path. Cross-tab updates aren't reflected, but
  that's a project-wide cache-coherency concern, not specific to this
  PR.
- **HIGH-4 tree.js size** — real architectural concern but follow-up
  only. Track on #47 widening (or a sibling extraction issue).
  Adding extraction in this PR would mix refactor with feature.
- **MEDIUM-7 tag API asymmetry** — agent claimed `addTag`'s parameter
  reassignment differed from neighbors, but verified the existing
  convention IS parameter reassignment (`addDirectory`, `renameDirectory`,
  `renameWorkflow`, `moveDirectory` all do `name = (name || "").trim();`).
  The defensive-copy difference between `getWorkflowTags` and
  `getWorkflowGraph` is intentional.
- **MEDIUM-8 top-level "Archive" name** — pre-existing semantic shared
  by toolbar Create-directory and save-modal "+ New directory."
  Restricting only the right-click new-dir entry would be inconsistent.
  Filing as a separate cross-cutting issue is the cleaner path.
- **LOW magic strings / modals.js size** — both queued for #47.

Validation
----------
- `python tools/preflight_release.py --skip manager-meta -v` PASS.
- `node --input-type=module --check` on all three modified JS files — clean.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 08:59:13 +02:00
malkuthroandClaude Opus 4.7 8c1e6e3d78 chore(sidebar): tidiness pass — toolbar factory, dropped dead code, store invariants (#60)
* chore(sidebar): tidiness pass — toolbar factory, dropped dead code, store invariants

Refactor pass against four pre-existing items on #47. No behavior change;
the diff is "extract helper + drop dead code + add comments" only. Held
back from PR #59 so feature and refactor stay isolated for review.

Changes
-------
- **`makeToolbarButton({iconClass, title, onClick})`** factory replaces
  the four near-identical button-construction blocks in `renderPanel`
  (export, new-dir, save-canvas, save-selection). Each block was 5–7
  lines of `createElement` + class/innerHTML/title/listener wiring;
  call sites collapse to 4-line factory invocations.
- **Dropped duplicate `.koolook-export-btn` CSS rule.** It was
  byte-for-byte identical to `.koolook-icon-btn` (minus the latter's
  `:disabled` styling, which the export button never hit). The export
  button now uses only `.koolook-icon-btn` via `makeToolbarButton`.
- **`buildFolder({path})` default removed.** Previously
  `path = null` with an `if (path && …)` runtime guard. Verified all
  callers (both inside `renderTree` and inside `makeSectionCtx`) always
  supply a non-empty string — the default was never reachable. Param
  is now required; runtime guards are dropped.
- **`workflowsCache` mutator invariants documented inline.** Four
  rules a future contributor needs before adding a new mutator:
  - Pair every mutate with a commit (or use `persistMutation`) — a
    mutator that "succeeds" without a paired commit silently drops the
    change on the next reload. We've already shipped + reverted that
    class of bug once; the comment names it explicitly.
  - Return `false` for no-op (collisions, missing source, validation
    failures), truthy for success. `persistMutation` reads this.
  - Mutate in place; don't return new structures.
  - Never replace `workflowsCache` itself except in seed/load paths.

Closes
------
Four checkboxes from #47 (architectural + code-tidiness):
- "Decompose `renderPanel` + extract `makeToolbarButton`"
- "Drop duplicate `.koolook-export-btn` CSS class"
- "`buildFolder` `path = null` default (unreachable) — drop"
- "Document `workflowsCache` invariants"

Remaining #47 items not in this PR (queued for a future widening):
- DOM helpers `el(tag, props, children)` + `modalLabel(text)` — wider
  refactor that touches modal/folder/leaf row construction.
- Magic-string lift (action sentinels in save modal, `?overwrite=true`,
  `pathStates` mini-DSL).
- `dir` / `dirName` / `directory` terminology pass.
- `dataset.expanded` boolean storage instead of string compare.
- UX items (`subcategoryFor` fallback, seed-failure toast, selection
  serializer stale-id case).

Validation
----------
- `python tools/preflight_release.py --skip manager-meta -v` PASS.
- `node --input-type=module --check` on each modified JS file — clean.
- Pure refactor: `git diff` is mechanical extraction + dead-code drop +
  comment additions. No semantic changes to existing flows; no new
  user-visible state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sidebar): address PR #60 review findings

Multi-agent review surfaced 2 MEDIUM + 3 LOW. Fixed 4 of the 5; skipped
the JSDoc-on-makeToolbarButton LOW because sibling factories (makeFolderRow,
makeNodeLeafRow, makeWorkflowLeafRow) also have no JSDoc — adding it just
on the new factory would be inconsistent local style.

Fixes
-----
- **MEDIUM** `makeToolbarButton` no longer uses `innerHTML` template
  interpolation. Icon span is built with `createElement` + `className` +
  `appendChild` instead. All current callers pass static literals, so this
  isn't a fix for an active vulnerability — it's helper-boundary hardening
  so a future caller passing dynamic data (workflow metadata, registry
  entry, etc.) can't inject markup. Also removes the file's only
  `innerHTML`-with-interpolation outside the empty-string clear at L157,
  matching the rest of the file's DOM-API style.
- **MEDIUM** Mutator-invariant rule #4 in workflows_store.js corrected to
  list all three legitimate `workflowsCache` rebind sites: module init,
  load/seed paths, AND `snapshotCache().restore()` during rollback.
  Previous wording denied that rollback rebinds, which would have led a
  future contributor reading the doc to either "fix" snapshotCache
  (breaking rollback) or distrust the entire invariant block.
- **LOW** `buildFolder({path})` now throws on `typeof path !== "string" ||
  !path` at function entry. Converts a silent state-corruption regression
  ("future caller forgets to pass path → `pathStates.set(undefined, …)`
  pollutes one shared key across all unkeyed folders") into a loud
  boundary failure. Promotes the PR description's "unreachable default"
  claim into a self-enforcing assertion.
- **LOW** `makeToolbarButton` doc comment tightened from "every toolbar
  button" to "the four icon-only toolbar buttons (export, new-dir,
  save-canvas, save-selection)" with explicit acknowledgment that the
  text-content `addBtn` (Add-to-favorites "+") stays hand-rolled.

Skipped (with rationale)
------------------------
- **LOW** Add JSDoc to `makeToolbarButton` — sibling factories
  `makeFolderRow`, `makeNodeLeafRow`, `makeWorkflowLeafRow` have no JSDoc
  either. Adding it just on the new factory would be inconsistent. If the
  whole DOM-helpers section gets JSDoc later (under #47's wider widening),
  this factory should be in scope at the same time.

Validation
----------
- `python tools/preflight_release.py --skip manager-meta -v` PASS.
- `node --input-type=module --check` on each modified JS file — clean.
- `grep -n "innerHTML" web/sidebar/tree.js` → only the L157 empty-string
  clear remains.
- `grep -n "workflowsCache *=" web/sidebar/workflows_store.js` → all
  rebind sites (init, load×5, rollback, seed) covered by the corrected
  rule #4.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 08:58:38 +02:00
malkuthroandClaude Opus 4.7 0d4c81295b chore(sync): --init flag for first-run dev-sync setup + Mac path docs (#61)
Background
----------
Setting up `scripts/sync_to_dev.py` on a fresh machine required two
pre-steps the script didn't help with: (1) editing `.env` to point at
a path that didn't exist yet, then (2) `mkdir -p` on that path before
the first sync would work. The script errored out cleanly with
"target does not exist" but didn't tell the user the obvious next
move was to create it. This was particularly silent on Mac where the
ComfyUI install path differs from the Windows examples in `.env.example`.

Changes
-------
- **`--init` flag** on `scripts/sync_to_dev.py`. When the target
  directory doesn't exist and `--init` is passed, the script creates
  it and proceeds with the sync. Without `--init`, it errors out with
  the same "first time on this machine?" hint pointing at the new flag.
- **Safety guards** on `--init`. Refuses (exit 3) if:
  - the parent directory doesn't exist, OR
  - the parent doesn't look like a ComfyUI `custom_nodes/` folder
    (heuristic: parent is named `custom_nodes` OR sits inside a
    directory named `ComfyUI`).
  This prevents a typo in `KOLOOK_COMFYUI_DEV_PATH` from quietly
  creating a `ComfyUI-Koolook/` somewhere unexpected on disk. The
  refusal message includes a manual escape hatch (`mkdir -p` + retry
  without `--init`) for the case where the parent really IS the
  ComfyUI install but doesn't match the heuristic.
- **Drop stale `k_easy_version.py`** from `RUNTIME_PATHS`. The file
  was deleted in v0.1.10 (PR #41); the script's `if not src.exists()`
  guard meant it was a no-op every run, but the entry was misleading.
- **`.env.example`** gains explicit macOS / Linux / Windows path
  examples for `KOLOOK_COMFYUI_DEV_PATH` and a 3-step first-run
  recipe (cp .env.example → edit → `--init`).
- **`docs/maintainers/dev-iteration-loop.md`** gains a "First-time
  setup on a new machine" section with the per-OS path table and the
  same 3-step recipe.

Verification
------------
- `python scripts/sync_to_dev.py --dry-run` against a missing target
  → exit 2, hint at `--init`.
- `python scripts/sync_to_dev.py --init` against a real ComfyUI
  `custom_nodes/` parent → creates the leaf, copies 9 entries, exits 0.
- `python scripts/sync_to_dev.py` (no flag) re-run after `--init` →
  re-syncs cleanly, idempotent.
- `python scripts/sync_to_dev.py --init` against `/tmp/not-custom-nodes`
  → exit 3, "parent does not exist".
- `python scripts/sync_to_dev.py --init` against an existing
  `/tmp/wrong-parent` (not custom_nodes) → exit 3, "doesn't look like
  a ComfyUI custom_nodes/ folder", manual escape hatch in the message.
- `python -c "import ast; ast.parse(open('scripts/sync_to_dev.py').read())"`
  → syntax OK.
- `python tools/preflight_release.py --skip manager-meta -v` → PASS.

No CHANGELOG entry — dev-tooling under `scripts/` doesn't ship to
users of the published package. Avoiding noise on the [Unreleased]
section that PRs #59 / #60 also touch.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 08:58:24 +02:00
malkuthroandClaude Opus 4.7 86a3edaf9e fix(sidebar): place inserted nodes at visible center on HiDPI displays (#62)
`placeAtCanvasCenter` in canvas_io.js was using `canvas.canvas.width` /
`.height` to compute the canvas-center coordinates for newly inserted
nodes. Those properties report the WebGL-style backing-store pixel
buffer dimensions, which on a HiDPI display (Retina/Mac, Windows DPI
scaling > 100%) are `devicePixelRatio` × the visible CSS-pixel
dimensions. LiteGraph's pan/zoom math operates in CSS pixels, so
halving the buffer width put the node at devicePixelRatio× the
visible-center distance from the graph origin — well off the right
edge of the viewport.

Symptom on Mac (Retina, devicePixelRatio = 2): clicking a node in
the Kforge Labs sidebar created the node, but it landed roughly off
the right edge of the visible canvas. The minimap showed it
appearing far from the user's current view; the canvas-center was
unaffected. Worse on zoomed-in / panned canvases.

Why it worked on Windows: most Windows desktops run at 100% DPI
(devicePixelRatio = 1), where buffer pixels and CSS pixels are
identical and the original math is correct.

Fix: use `canvas.canvas.clientWidth` / `.clientHeight` (CSS pixel
dimensions). Falls back to `.width` / `.height` if the canvas hasn't
been laid out yet (during early init), preserving the prior behavior
in that edge case.

No other call sites in the codebase use the same backing-store-pixel
pattern (verified via `grep -rn "canvas\.canvas\.width"` in web/).
The bug was localized to this one helper.

Verification
------------
- `node --input-type=module --check` on canvas_io.js — clean.
- Live test on Mac (synced via `scripts/sync_to_dev.py` to a real
  ComfyUI install): clicking a sidebar node row now creates the node
  near the canvas center across multiple zoom levels and pan offsets.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 08:58:12 +02:00
malkuthroandClaude Opus 4.7 e95c12f6a9 docs(forks): finalize de-vendoring audit trail (closes #28) (#55)
* docs(forks): finalize de-vendoring audit trail (closes #28)

The v0.1.4 / v0.1.5 release cycle untracked six vendored third-party
trees from MAIN's git index (`git rm -r --cached`, then added to
`.gitignore`), which fixed the Comfy Registry's static-scanner
miscount (44 -> 8 nodes in ComfyUI-Manager) and shrank the published
archive by ~3.7 MB. That part of issue #28 was already shipped.

Issue #28's remaining acceptance criteria were the audit-trail bits:
- list each removed tree in `forks/THIRD_PARTY.md` with upstream URL
  + license, and
- record any modified files that were promoted into proper fork
  wrappers under `forks/<package>/versions/<vX_Y_Z>/`.

This commit adds a "De-vendored upstream code (untracked in v0.1.4 /
v0.1.5)" section to `forks/THIRD_PARTY.md` covering all six trees:
- `upscaler_FIX/github_repos/ComfyUI-SuperUltimateVaceTools/`
- `upscaler_FIX/github_repos/ComfyUI-multigpu/`
- `upscaler_FIX/github_repos/debugg/`
- `nuke_CAM_exporter/_Utils-CAM-track/Github-Repos/Comfyui-Animatedfiff-evolved/`
- `nuke_CAM_exporter/_Utils-CAM-track/Github-Repos/Comfyui-kjnodes/`
- `nuke_CAM_exporter/wrapper_FIX/` (kijai/ComfyUI-WanVideoWrapper)

None of the six were promoted to tracked fork wrappers — the
`wrapper_FIX/` work in particular was incomplete (token-count
alignment fix between Wan 14B and FunCamera) and the upstream pin was
never recorded, so it'll be redone from scratch against current
upstream when needed.

Pure docs change. No package code touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(forks): full #28 closure — add manifest entries + verbatim-paths note

Addressing /review-pr findings on the previous commit b029570:

HIGH (CONFIRMED): "Closes #28" was overstated against issue #28's
literal acceptance criterion (2) — registering each removed tree in
`forks/forks_manifest.yaml` with `source_repo`/`source_ref`/
`pinned_commit`/`local_paths`. The previous commit only delivered the
THIRD_PARTY.md audit table (criterion (3)).

This commit adds six `status: "removed"` entries to
`forks/forks_manifest.yaml` (one per de-vendored tree) with:
- `source_repo`: known upstream URL where determinable, "unknown" with
  vendor-time-no-pin caveat otherwise
- `source_ref` / `pinned_commit: null`: honest about missing pins
- `local_paths`: actual former paths (matches v0.1.0 git tree)
- `removed_at: 2026-05-03`, `removed_in_release: "0.1.4 / 0.1.5"`
- `license`: mirrors THIRD_PARTY.md ("unverified" / "GPL-3.0" / "n/a"
  / "assumed GPL-3.0" — last verified 2026-05-03 for kjnodes)
- `notes`: cross-link back to THIRD_PARTY.md "De-vendored upstream
  code" section

LOW (CONFIRMED but no-action): typo `Comfyui-Animatedfiff-evolved` and
mixed `Comfyui-` / `ComfyUI-` casing on rows 114-115 of THIRD_PARTY.md.
Verifier confirmed both reflect actual former on-disk folder names
(via `git ls-tree v0.1.0`) and are intentional historical record. Added
a one-paragraph preamble note above the table making this explicit so
future readers don't try to "fix" the typo and break the audit trail.

HIGH (DOWNGRADED to LOW by verifier): "License unverified shipped to
PyPI / Comfy Registry" claim was a false-positive premise — verifier
checked `gh run list --workflow=publish.yml` and found every push-to-
main publish for v0.1.0/v0.1.1/v0.1.3 failed; the first successful
Comfy Registry publish was the v0.1.4-prep dispatch on 2026-05-03,
post-untrack. The residual concern (GitHub release source tarballs)
is real but standard for any post-untrack project; preamble now
states this explicitly so future readers don't mis-read "License
unverified" as "shipped to users without a license check".

Pure docs / metadata. No package code touched.

Closes #28.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:41:47 +02:00
malkuthroandClaude Opus 4.7 c024a94e38 feat(sidebar): recursive subdirectories under workflows + rebrand to Kforge Labs (#54)
* feat(sidebar): recursive subdirectories under workflows + rebrand to Kforge Labs

## Recursive subdirectories

Workflow directories can now host nested subdirectories at arbitrary
depth. Each nested directory behaves like a top-level one: it can hold
workflows, its own Archive subfolder, and further subdirectories.

- **Create**: right-click a directory → "Create subdirectory…"
- **Save into**: pick the path from the save modal's flat directory
  dropdown (`UP-scale / Type-A`)
- **Move across**: right-click a workflow → "→ <other path>" submenu
  lists every other path in the tree
- **Reserved name**: subdirectory names cannot be "Archive" (case-
  insensitive) at non-root levels — collides with the synthetic Archive
  folder rendering. Top-level "Archive" still allowed (root has no
  archived workflows of its own).

### Schema (recursive, back-compat)

  workflowsCache = { directories: { [name]: DirNode } }
  DirNode = { workflows: { [wfName]: WorkflowEntry }, directories: { [name]: DirNode } }

Pre-v0.3 stores without the nested `directories` field still load fine —
`normalizeWorkflowsStore` treats a missing `directories` as `{}` and
adds the empty field during normalization. No data loss on upgrade.

### Path-based store API

Every mutator/lookup now takes a `path: string[]`:

  addDirectory(parentPath, name)
  renameDirectory(parentPath, old, new)
  deleteDirectory(parentPath, name)
  saveWorkflowEntry(path, wfName, graph)
  archiveWorkflow(path, wfName) / unarchiveWorkflow(...)
  renameWorkflow(path, old, new) / deleteWorkflow(path, wfName)
  moveWorkflow(srcPath, wfName, dstPath)
  getWorkflowGraph(path, wfName)

New helpers: `listDirectoryNames(parentPath = [])`,
`listAllDirectoryPaths()` (DFS), `pathsEqual(a, b)`. `dirOf([])` returns
a synthetic root wrapper so callers don't need a special-case for root.

### UI changes

- **Save modal**: directory dropdown is now a flat list of every path in
  the tree (`JSON.stringify(path)` values, `path.join(" / ")` labels).
  "+ New directory…" creates a top-level dir; subdirs go via right-click.
- **Directory header counts**: now show recursive descendant total
  (active + archived + all subtree). A parent with empty direct
  workflows but populated subdirs no longer shows "0".
- **Save flow**: `saveAndToast` pins every ancestor (`workflows`,
  `workflows/seg1`, `workflows/seg1/seg2`, …) so a save into a deeply
  nested path opens the whole chain.
- **Render order per directory**: subdirs first → Archive folder →
  active workflows last (so the freshest version sits closest to the
  bottom).

## Rebrand: "Curated Nodes" → "Kforge Labs"

- `TAB_TITLE`: "Curated Nodes" → "Kforge Labs"
- `TAB_TOOLTIP`: "Curated selection of ComfyUI nodes" →
  "Kforge Labs — curated nodes & workflows"
- Canvas right-click menu item: "Add to Curated Sidebar" → "Add to Kforge Labs"
- Empty-state hint in the curated-nodes section follows the new label

`TAB_ID` (`koolook.curatedNodes`) and the `app.registerExtension` name
(`koolook.curated_sidebar`) are deliberately unchanged so existing
per-user tab state (pinning, tab order, idempotency sentinel) is
preserved.

## Files

- web/sidebar/constants.js (rebrand)
- web/sidebar/canvas_menu.js (rebrand)
- web/sidebar/canvas_io.js (loadWorkflowOntoCanvas takes path)
- web/sidebar/workflows_store.js (recursive schema + path-based API)
- web/sidebar/modals.js (save modal flat-path picker)
- web/sidebar/tree.js (gather recursion + recursive renderer + path-based context menus)
- docs/maintainers/workflows-sidebar.md (subdirectories section + recursive JSON shape)
- CHANGELOG.md ([Unreleased] entry)

Encapsulation invariants preserved: `workflowsCache` only in
`workflows_store.js`; `pathStates` only in `tree.js`.

Closes the maintainer's outstanding feature request for subfolders under
workflow directories.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sidebar): show empty directories in tree when no search is active

User-reported: right-click → Create subdirectory → name → Submit
appeared to do nothing. The directory was actually created in the cache
(and showed up correctly in the save modal's path picker), but
gatherDirsAt was dropping any directory with zero matched workflows AND
zero matched descendants — including freshly-created empty subdirs.

Fix: scope the empty-drop check to "search active" only. With no search,
every directory in the cache renders. With a search, the filter still
collapses paths that contributed no hits, so search results stay clean.

Verified via Node dry-test (.tmp_subdir_test.js, since deleted):
- Pre-fix: addDirectory(['UP-scale'], 'Test') succeeds; gather('') drops Test
- Post-fix: gather('') includes Test; gather('hires') still drops empties

Also confirmed there is NO case-sensitivity restriction — 'Test' and
'test' coexist as separate directories. The user's earlier observation
that "only the lowercase one shows" was the empty-dir bug masquerading
as a case issue (whichever directory had been saved into earlier was
visible; the other was hidden).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sidebar): cascading directory picker in save modal (multi-step)

User feedback: the flat path list was unusable with deep nesting — every
existing path appeared in one giant dropdown, you couldn't pick a parent
and then narrow to a child. Replaced with cascading selects.

Behavior:
- Top-level select shows existing top-level dirs + "+ New directory…"
- Picking a top-level with children reveals a level-2 select. The first
  option is "(save in <path>)" which stops drilling at this level.
  Following options are the children.
- Picking a level-2 child with grandchildren reveals a level-3 select
  with the same shape, etc., to arbitrary depth.
- "(save here)" at any level commits the save at that depth.
- Picking a different top-level / parent collapses every cascade level
  below it and rebuilds.
- `defaultDir` (a path array) auto-drills the cascade to that path on
  open, with onCascadeChange recursing through pre-selected levels.

The save modal's Base-on-existing / Action / Workflow-name sections
re-derive from `getSelectedPath()` whenever any cascade level changes,
so they reflect the current target dir.

Verified via Node dry-test (.tmp_cascade_test.js, since deleted) over 6
scenarios: fresh open, deep defaultDir pre-selection, level-2 collapse,
top-change-to-leafless-dir, "+ New directory…" + name input, manual
drill into a no-children leaf.

The store API is unchanged — `saveWorkflowEntry(path, wfName, graph)`
still takes a path array, and the cascade just produces the same array
through interactive selection instead of a flat dropdown.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(sidebar): right-click Archive folder → Delete archive (N items)

Adds a context menu to the synthetic Archive folder that bulk-removes
every archived workflow in its parent directory. Active workflows at the
same level are untouched. The menu only appears when archivedCount > 0
(Archive folder is synthetic and only renders when there are archived
workflows present), so the no-op path is unreachable in practice — the
clearArchive function still defends against it (returns false if no
archived entries to remove or directory missing).

User-asked gap from the subdirectories review. Other adjacent gaps
considered:
- Restore-all from archive: SKIP. Each archived entry has a
  "(archived <ts>)" name suffix; bulk-restoring leaves messy long names
  AND can collide with the still-active version. Per-item is the right
  granularity.
- Move a real Directory across paths: SKIP for this PR. Useful but needs
  cycle prevention + path-prefix updates; separate scope.
- Move workflow to a NEW directory inline: SKIP. Two-step workaround
  (right-click → Create subdirectory, then move) works.
- Move synthetic Archive folder: N/A. The Archive folder is auto-derived
  from `archived: true` flags; nothing to move.

Verified via Node dry-test (deleted): clearArchive correctly removes
only archived entries, preserves active ones, returns false on
empty-archive and missing-dir paths.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sidebar): pull 'Base on existing' candidates from ancestors too

Reported regression: navigating into an empty subdirectory (e.g.
UP-scale → seedvr2) disabled "Use existing name" / "Modify existing
name" because getActiveWorkflowsInCurrentDir only looked at the leaf
dir's workflows. With nested dirs, leaf dirs are often empty (just
created via right-click), making those actions unreachable for the
common "save a variant of an existing workflow into a new subdir" flow.

Fix: rename to getCandidatesForBase and walk the destination path from
deepest up to root, collecting active workflows at every level. Dedup
by name (deepest wins). The dropdown shows the bare name when the
candidate is from the current dir, or "name  ·  in <path>" when it
comes from an ancestor — so the user knows which dir they're basing on.

The submit path is unchanged: baseSelect.value is still the bare name,
which feeds the workflow-name field for "Use existing"/"Modify existing"
actions. The Save destination path is whatever the cascade resolves to,
not the ancestor path — so basing UP-scale/seedvr2's new save on
"Hires-FIX-wanT2V-v01  ·  in UP-scale" creates UP-scale/seedvr2/Hires-
FIX-wanT2V-v01 (no archive happens because the destination didn't have
that name before).

Verified via Node dry-test (deleted): empty subdirs now expose the
parent's active workflows; deep empties expose every ancestor's actives;
non-empty leaves show their own first then ancestors with path
disambiguation; archived entries excluded; alphabetical within each
level.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(sidebar): drag-and-drop moves in workflows tree (Tier 1)

User: "if we can fix this [DnD] then everything is fixed". Tier 1 ships
the moves-only piece — drag-to-move workflows, drag-to-nest dirs, drag
onto Archive to archive. No custom-ordering schema change (sort within a
level stays alphabetical); that would be Tier 2.

Drag/drop semantics:
  workflow row  → directory row    move workflow (no-op if same dir)
  workflow row  → Archive folder    archive in that dir (move first if
                                    cross-dir, all in one persist)
  directory row → directory row    nest dragged dir as child of target

Cycle prevention in moveDirectory: rejects same-parent, dropping a dir
into itself, or into any of its descendants. Plus collision (target
already has a sibling with that name) and reserved-name "Archive" at
non-root.

Implementation:
  - workflows_store.js: new moveDirectory(srcParentPath, name,
    dstParentPath) with isPathDescendantOrSame() helper.
  - constants.js CSS: grab/grabbing cursor on draggable rows; blue
    outline + tinted background on .koolook-drop-target.
  - tree.js: decorateDraggable(row, payload) and decorateDropTarget(row,
    target) helpers; handleDndDrop dispatcher; threaded through
    makeFolderRow / makeWorkflowLeafRow / buildFolder / makeSectionCtx
    so renderGatheredDir can attach DnD opts via the section ctx.

DnD uses the native HTML5 Drag-and-Drop API (no libs). MIME type
"application/x-koolook-row" filters out file/external drags.

Verified via Node dry-test (deleted) over 7 scenarios:
  1. legal cross-parent move (Type-A from UP-scale to Depth)
  2. cycle: parent → its child → blocked
  3. same-parent → blocked (no-op)
  4. cycle: dir → its own descendant → blocked
  5. name collision in destination → blocked
  6. legal sibling-becomes-child nest
  7. moving a dir literally named "Archive" to non-root → blocked

Visual feedback: drop targets get .koolook-drop-target on dragover
(blue outline + tinted background), removed on dragleave/drop. Failed
drops surface "Could not move (cycle, collision, …)" toast through the
existing persistMutation onNoOp path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sidebar): hide Action dropdown entirely when no base to act on

User feedback: when saving into a directory whose path has zero active
workflows in any ancestor (genuinely empty case), the Action dropdown
showed three options with "Use existing name" and "Modify existing
name" disabled. Disabled <option> renders subtly across browsers — most
users just see "New name" with no obvious why. They proposed two
options: (1) gray out clearly, or (2) hide the dropdown entirely. Going
with (2) — Action only matters when a base candidate exists; without
one, the only useful flow is "type a name and save," which the
Workflow Name field already provides.

So now:
- hasBase=true (current dir or any ancestor has active workflows) →
  full Action picker, all three options enabled.
- hasBase=false (truly empty path, all the way up to root) →
  Action label + dropdown hidden; underlying actionSelect.value pinned
  to "new" so submit takes the by-name path through the Workflow Name
  field.

The ancestor-pull fix from e7da864 means this empty branch only fires
for genuinely-fresh paths (a brand-new top-level + subdir created
together with no workflows anywhere). Common case is hasBase=true.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sidebar): apply verified PR-review findings on #54

Verified review (3 review agents → synthesizer → verifier) on the
recursive-subdirectories + Kforge Labs PR flagged 3 HIGH / 1 MEDIUM /
1 LOW. All confirmed; no false positives. Applying every actionable
fix in one commit.

[HIGH] CHANGELOG disclosure for two undocumented Save-modal shifts:
  1) Base on existing now walks ancestor paths leaf-up, dedupes deepest-
     wins, labels ancestor entries "<name>  ·  in <path>".
  2) Action dropdown is hidden (not just disabled) when no base
     candidate exists; underlying value pinned to "new" so submit takes
     the by-name path.
Both behaviors landed in late commits (e7da864 and cd9b2c3); the [Unreleased]
entry now describes them under ### Changed alongside the Kforge Labs
rebrand bullets.

[HIGH] Self-drop directory misleading toast (tree.js handleDndDrop):
The same-parent guard caught dir-on-its-own-parent but missed dir-on-
its-own-row, because the dir's row carries `target.path =
[...srcParentPath, name]` which doesn't equal `srcParentPath`.
moveDirectory's cycle check would still reject the move, but the user
got "Could not move directory (cycle, name collision, or invalid
target)" for what is just a no-op gesture. Adds a second early-return
matching `[...srcParentPath, dirName]` against `target.path` so
self-drops are silent.

[MEDIUM] addDirectory(undefined, "x") crash hardening: moveDirectory
already had `Array.isArray` guards on its parent-path inputs;
addDirectory lacked the equivalent and would throw on
`parentPath.length` if a future caller ever passed `undefined`. Adds
the symmetric guard.

[LOW] Drops the dead `isArchiveReservedName` export — the predicate
was never imported. Reserved-name enforcement remains inline in every
mutator that creates or renames a directory. A short comment notes the
removal so future contributors know to revive the export only when a
caller (e.g. modal pre-validation) actually needs it.

Verified via Node dry-test (deleted) covering:
  - addDirectory(undefined / null / "string" / []) — all behave correctly
  - Self-drop on root 'A' → "no-op-self-drop" (silent)
  - Legal cross-parent move 'A' → 'B' → "moved"
  - Self-drop on nested 'B/A' → "no-op-self-drop"
  - Drop 'B/A' onto own parent 'B' → "no-op-same-parent"

Spec alignment: every fix was verified against PR intent. The two
Save-modal shifts are organic late-stage refinements, not regressions —
disclosure is the only ask and is now delivered. The self-drop guard
and Array.isArray hardening tighten contracts that were already the
clear intent. The dead-export drop is housekeeping.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 20:33:54 +02:00
malkuthroandClaude Opus 4.7 a609e75d40 refactor(web): tree pipeline as section-registry with two-tier API (#52)
* refactor(web): tree pipeline as section-registry with two-tier API

Implements issue #50. Behavior-preserving — same DOM, same path-state
keys, same render cadence, same context menus. The new engine deepens
the tree module: a 3-function public surface hides the section
iteration, divider insertion, forceExpanded threading, path prefixing,
empty-state placeholders, and pathStates lifecycle.

Public surface (3 functions, all module-private — only renderPanel is
exported):
  - addSection(spec)        — register a section descriptor
  - renderTree({treeEl,query}) — single render entry point
  - pinExpanded(paths)      — pin folders open for the next render

Internals:
  - SECTIONS array (declaration order = render order)
  - pinnedPaths Set (consumed every render)
  - makeSectionCtx → builds ctx.folder({...}) / ctx.leaf({row}) with
    automatic path prefixing (sections write segment-relative paths;
    engine produces ${section.id}/<seg>/...)
  - validPaths Set populated as folders render; pathStates pruned at
    end of each render against validPaths ∪ pinnedPaths — fixes the
    unbounded-growth bug from renames/deletes leaving stale keys

Built-in sections:
  - "nodes": curated-nodes section, merges gatherNodesByRepo +
    gatherUserPickPacks into one displayed group with auto-pack and
    user-pick subgroups (same UX as before)
  - "workflows": directories with Archive sub-folders and right-click
    context menus

Save flow:
  - saveAndToast now calls pinExpanded(["workflows", "workflows/${dir}"])
    before persistMutation; the persist's notifyWorkflowsChanged fires
    a re-render that consumes the pin and opens the destination folder

Adding a new section is now ~12 lines (simple flat list) or ~30 lines
(advanced tree with row interactions) — the engine handles cross-cutting
concerns. Foundation for tags / recents / pinned / templates features.

Closes #50.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sidebar): scope pathStates prune to rendered sections + always apply pins

Two HIGH issues from the multi-agent review on PR #52:

1) Pruning was over-broad. When section A is gated empty by a search
   filter, its (legitimate) pathStates keys were getting nuked because
   they weren't in `validPaths` — clearing the search reverted the
   user's prior expansions to startExpanded defaults. Fix: track which
   sections actually rendered this pass (`renderedSectionIds`); prune
   only keys whose owning section rendered.

2) `pinnedPaths` could leak across renders when the cross-section
   search-empty branch took an early return without consuming the pin
   set. A pin from one save would later be applied to an unrelated
   render. Fix: drop the early return; phase 3 (prune + pin apply) now
   always runs.

Also tightens code-quality LOW findings:
- `let pinnedPaths` -> `const pinnedPaths` + `.clear()` after snapshot
  via `new Set(pinnedPaths)`.
- Add `@typedef SectionCtx` and `FolderOpts` JSDoc blocks (referenced
  by `SectionSpec.build` but missing in the initial commit).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 18:12:44 +02:00
malkuthroandClaude Opus 4.7 24dee7ee1b chore(skills): rename project /menu to /menu-comfy to avoid global collision (#53)
The project's `menu` skill at `.claude/skills/menu/` shared the same `/menu`
trigger as the user-level `~/.claude/skills/menu/` skill, so the global one
won and the project inventory never surfaced. Renamed the project skill to
`menu-comfy` so both can coexist.

- `git mv .claude/skills/menu .claude/skills/menu-comfy`
- Frontmatter `name`, header, trigger phrases, output example, and procedure-
  step reminder all switched to `menu-comfy` / `/menu-comfy`.
- Two intentional references to the global `/menu` retained — they document
  the deliberate split so future contributors don't "fix" them back.
- `docs/maintainers/README.md` companion-skills table row updated, with a
  one-line note about the collision split.

Behavior change: `/menu-comfy` now lists project skills; `/menu` continues
to list the global curated set. Docs-only — no runtime/code change.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 18:07:57 +02:00
malkuthroandClaude Opus 4.7 e03523faf9 refactor(web): split koolook_sidebar.js into 7 sidebar modules (#51)
* refactor(web): split koolook_sidebar.js into 7 sidebar modules

Pure mechanical split — zero behavior change. Implements the design from
issue #49 (common-case layout with patchCanvasMenu extracted).

Old: web/koolook_sidebar.js (1973 lines, monolithic)
New: web/koolook_sidebar.js (48-line entry) + 7 modules in web/sidebar/

  - constants.js (130) — REPOS, TAB_*, storage keys, event names, URLs,
    ensureStyle, toast, compareNames
  - picks_store.js (117) — localStorage user picks: load/save/add (tri-state)
    /remove/notify/seed/export
  - workflows_store.js (347) — workflowsCache + persistMutation + snapshotCache
    + commit + persist*/load*/seed* + in-memory ops; cache is module-private
  - canvas_io.js (192) — serialize*/canvasIsNonEmpty/loadWorkflowOntoCanvas
    /insertNode/getSelectedNodeTypes
  - modals.js (384) — makeModalShell + showInputModal/showConfirmModal
    /showSaveWorkflowModal/showContextMenu
  - canvas_menu.js (45) — patchCanvasMenu (idempotent LGraphCanvas patch)
  - tree.js (825) — pathStates (module-private) + gatherers + DOM row
    factories + buildFolder + workflow/dir context menus + rebuildTree
    + renderPanel

Encapsulation invariants verified:
  - workflowsCache: zero references outside workflows_store.js
  - pathStates: zero references outside tree.js
  - All 8 files pass node --check
  - CSS string byte-identical to original
  - URL constants resolve to same targets after the extra `..` adjustment

Documentation updated: dev-iteration-loop.md and workflows-sidebar.md
now point pathStates references at web/sidebar/tree.js. Other doc
references to koolook_sidebar.js as the entry remain accurate.

Closes #49.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* refactor(web): drop unreachable defensive guard in gatherWorkflows

Review feedback on #51: the `if (!dir) continue;` after `dirOf(dirName)`
is unreachable — `listDirectoryNames()` enumerates the same module-private
`workflowsCache.directories` keys that `dirOf()` reads, synchronously,
within a single event-loop turn. Removing the guard restores strict
"byte-identical move" semantics for the file-split refactor.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 17:47:20 +02:00
malkuthroandClaude Opus 4.7 cc6bc47866 docs+tooling: VAE user guide + versioning ref + 3 skills + preflight CI (#48)
* docs+tooling: VAE user guide + versioning ref + 3 skills + preflight CI

Documentation
- New user-guide tree: docs/user_guide/nodes/radiance_koolook_v2_3_3/
  with README, per-node pages for Easy_hdr_VAE_encode/decode, an
  encode/decode pairing guide (4 worked examples + ASCII connectivity
  diagram), and an HDR modes deep-dive (per-pixel comparison table).
  Folder name mirrors the code path under forks/radiance_koolook/versions/.
- New cross-cutting reference: docs/reference/versioning.md explains the
  three independent version axes (pack version, fork wrapper version,
  upstream pinned commit), when each one bumps, and which skill/doc
  owns it. Plus the v1/v2 node-ID suffix policy.
- Cross-links updated in docs/user_guide/README.md, docs/reference/
  README.md, docs/maintainers/README.md.

Skills (all under .claude/skills/)
- docs-sync — controlled find-and-replace for version tags across docs,
  code, and manifests; --scope and --dry-run flags.
- menu — lists all project skills with one-liners.
- preflight-release — companion doc for tools/preflight_release.py.

Pre-flight tooling
- tools/preflight_release.py — four checks, exit-code-driven for CI:
  (1) static AST extraction of NODE_CLASS_MAPPINGS keys (no torch needed),
  (2) stub-VAE dispatch test verifying all 5 rank/VAE-type branches,
  (3) drift check against ComfyUI-Manager's extension-node-map.json
      (catches the issue #44 phantom-nodes class of bug automatically),
  (4) saved-workflow regression check across tests/workflows/*.json.
- .github/workflows/ci.yml — new "Pre-flight Release" job runs checks
  1, 2, and 4 on every PR; check 3 (manager-meta) skipped pending the
  upstream issue #44 fix.
- tests/workflows/ — 2 starter fixtures (Depth-Anything3, Hires-FIX-wanT2V)
  + README documenting the contribution convention. Coverage grows as
  new fixtures are added; no code changes needed.

All four offline pre-flight checks pass on this branch. The
manager-meta check correctly identifies the 39 phantom IDs documented
in issue #44.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(preflight): satisfy ruff lint + bandit security gate

* fix(preflight): address blocking review findings

Three fixes from /review-pr feedback on this PR:

1. Sanitize tests/workflows/*.json — removed 4 unique absolute paths that
   leaked internal job/client info ('baconx/Toy-Hix/9110',
   'menneskehavn_3029', etc.). Replaced with placeholders like
   'path/to/sequence_a' and 'path/to/source_video'. Both fixtures still
   parse as valid JSON; grep for the old patterns returns zero hits.

2. Broaden the Koolook-ID heuristic regex from '^easy_[a-z]' to
   '^easy_[A-Za-z]'. The old pattern excluded 'easy_ImageBatch' (capital I
   after the underscore), which would have silently bypassed the
   workflow-fixture stability check for that node. Verified the new
   pattern correctly matches it.

3. Wrap check_dispatch() body in try/finally and track every sys.modules
   key the function injects in a list, so cleanup runs on the success
   path AND on any exception during exec_module / VAE-constructor
   execution. Previously only the three torch* stubs were popped, never
   the three _pf_pkg* entries — and even those weren't popped on
   exception paths. Verified zero leaked modules after the function
   returns.

The five dispatch test cases were factored into a helper
_check_dispatch_run_cases() so the try/finally wrapping stays focused on
module injection.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(preflight): address remaining review findings

Follow-up to 7844037 — picks up all the HIGH / MEDIUM / LOW review
findings that were deferred. Pre-flight still passes locally.

HIGH
- Extract check_dispatch (~195 lines) to tools/_preflight_dispatch.py.
  Main script drops from 680 → 508 lines. Module exposes a single
  run(verbose, repo_root) -> (bool, list[str]) entry; main script keeps
  the [2/4] header + PASS/FAIL line.
- Install _HttpsOnlyRedirectHandler that re-validates each redirect
  hop's scheme. Closes the gap that urlopen would otherwise follow a
  30x to http:// or file:// silently. The Bandit B310 suppression
  comment now accurately describes the protection.
- check_static docstring now honestly states it only walks
  NODE_CLASS_MAPPINGS literal dicts and does NOT apply
  NAMESPACE_SUFFIX / SKIP_VERSION_SUFFIX logic. Notes that the workflow
  check (4) is the safety net that surfaces drift the day a non-skipped
  ID lands.
- check_workflows now FAILS when the fixtures dir exists with a README
  but contains no *.json files — previously silently SKIPPED. If
  someone deletes fixtures or typoes the dir, that's a regression in
  test coverage and should fail loudly.

MEDIUM
- check_manager_meta validates the upstream entry shape before diffing.
  Previous code silently produced an empty set if the schema changed,
  reporting every one of our IDs as 'missing'. Now SKIPs with a clear
  message when shape is unexpected.
- check_workflows accumulates total_koolook_refs inside the main loop;
  drops the redundant second-pass that re-read every fixture file just
  to compute the summary count.
- _extract_workflow_node_types uses explicit detection: a canvas-format
  file with nodes: null no longer falls through to API-format and pulls
  class_type from unrelated keys.
- check_static return type simplified from (bool, set, list) to
  (bool, set). Third element was discarded at every call site.
- check_workflows now does two-pronged Koolook-ID matching: heuristic
  (catches drift even if static missed an ID) plus actual_ids (catches
  IDs that don't fit the heuristic, e.g. a future Koolook node without
  Easy / easy_ / koolook in its name).

LOW
- ANSI escape codes named in a constants dict (_ANSI["green"] etc.)
  instead of bare string literals.
- Removed dead 'cases: list[tuple[str, callable]] = []' initializer
  (was shadowed three statements later).
- check_static's per-ID listing now gated behind --verbose, matching
  check_dispatch and check_workflows.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 17:31:36 +02:00
malkuthroandClaude Opus 4.7 b7b34de7e2 feat(sidebar): curated nodes + workflows tab with archive (#45)
* feat(sidebar): add curated nodes sidebar tab with favorites

New "Curated Nodes" sidebar tab. Shows Koolook nodes auto-grouped by
subcategory under a "Nodes (favorites)" root. Users add favorites from
any installed pack from inside ComfyUI via:

- "+" button: adds the currently selected canvas node(s)
- Right-click any canvas node -> "Add to Curated Sidebar"

Picks persist in browser localStorage (per-browser, survives ComfyUI
restarts). Hover any entry -> "x" to remove. Search filter auto-expands
all folders so matches stay visible; pack/category folders are
collapsed by default otherwise to keep the panel compact.

"Download" export button copies current picks as JSON suitable for
web/curated_defaults.json -- committed to ship as the package's
defaults. On first ComfyUI load with empty localStorage, the seeder
reads curated_defaults.json into localStorage so fresh installs get the
curated set pre-loaded. Existing users keep their localStorage
untouched.

Pure JS, no build step. Uses the documented
app.extensionManager.registerSidebarTab API (stable since
ComfyUI_frontend v1.2.4).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(sidebar): document curated-defaults workflow + seed initial 9 picks

Adds docs/maintainers/curated-sidebar.md covering the full Export ->
paste -> commit flow for shipping distributed favorites with the
package, including storage-key reference, reset procedure, and the
intentional "seed once per browser" semantics.

CLAUDE.md gets a short pointer so the trigger ("update curated
defaults" or pasting an exported picks JSON) is discoverable on the
agent side too. Maintainer docs index linked.

Seeds web/curated_defaults.json with the maintainer's first 9 picks
(GetImageSizeAndCount, GetNode, ImageScaleBy, LoadEXR, NukeConstant,
NukeOCIOColorSpace, SaveEXRFrames, Text Multiline, easy showAnything).
Fresh installs from this version forward will get them pre-loaded into
their localStorage on first ComfyUI launch; existing users' picks are
untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* docs(sidebar): trim curated-sidebar.md to a one-screen quick reference

Drops the verbose data-flow diagram and the localStorage-vs-userdata
rationale. Keeps the four action steps, the storage-key map, the reset
block, and the four "things that surprised us" footnotes capturing the
non-obvious bits (paste is manual, other users can't reach the repo,
seed-once-per-browser, no publish on web/ pushes). 81 lines removed,
20 added; same file path so existing links still resolve.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(sidebar): workflows section with archive + iteration docs

Adds a Workflows section to the Curated Nodes sidebar tab, sharing the
search field and tree pattern with the existing nodes feature.

Capabilities:
- Save entire canvas or current selection as a workflow.
- Save modal: New name / Use existing name (archive previous) /
  Modify existing name. The Workflow-name field is shown only when
  needed; "Base on existing" dropdown drives prefill for Use/Modify.
- Auto-archive on same-name save: previous version renamed with
  timestamp suffix and flagged archived; new entry takes the slot.
  Archive sub-folder appears in each directory only when populated and
  is rendered above the active list, so the latest sits at the bottom.
- Right-click on workflow: Load / Rename / Move to other directory /
  Move to archive (or Restore from archive) / Delete.
- Right-click on directory: Rename / Delete (with confirm if non-empty).
- Loading a workflow names the tab via loadGraphData's 4th arg
  (verified against ComfyUI_frontend src/scripts/app.ts) so the tab
  flips from "Unsaved Workflow (N)" to the saved name.

Storage: ComfyUI /userdata/koolook_workflows.json (primary), with a
localStorage fallback if /userdata is unreachable. Distributed defaults
seeded from web/workflow_defaults.json on first ComfyUI load with empty
workflow data -- same one-time-per-install pattern as
curated_defaults.json.

Other panel changes:
- Three labeled rows above the tree (search; NODES action bar;
  WORKFLOWS action bar), with thin dividers between them and before
  the tree.
- Folder expansion state preserved across re-renders via a pathStates
  Map; saving never collapses the directory you were viewing.
- Alphabetical (case-insensitive) sort everywhere via a single
  compareNames helper -- pack names, subcategories, directories,
  workflow names, dropdown options.
- "Use existing name (archive previous)" action label includes the
  parenthetical hint about archive behavior.

Docs:
- New docs/maintainers/dev-iteration-loop.md captures the
  dev-sync->restart->verify pattern, agent trigger phrases, persistence
  boundaries, and push/publish gates. First read when returning to UI
  work after a break.
- New docs/maintainers/workflows-sidebar.md covers workflow storage,
  action semantics, archive behavior, and reset procedure.
- docs/maintainers/README.md indexes both.
- CLAUDE.md "dev-sync" section now points at the iteration-loop doc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sidebar): address review findings — seed gating + commit checks

Targets the data-integrity findings from the multi-agent PR review:

- BLOCKING: seed flag is now only set after the underlying save/persist
  succeeded. `saveUserPicks` returns a boolean; both seeders bail out
  (leaving the flag unset for next-load retry) on storage failure.
  Previously a transient storage failure during first-load could
  permanently strand a user with an empty store and the seeded flag set.

- BLOCKING: `normalizeWorkflowsStore` now strictly validates each
  workflow entry — drops entries missing a graph object, coerces
  `archived` to a strict boolean (so a stray "false" string can't
  accidentally hide a workflow), and logs the dropped count.

- HIGH: save handlers now check `commit()`'s return value and surface a
  "Save failed" toast when both /userdata and the localStorage fallback
  rejected the write. Previously the user saw "Saved" even though
  nothing was persisted. The canvas and selection handlers share a
  `saveAndToast` helper to keep the failure path consistent.

- HIGH: `saveCanvasBtn` now checks `canvasIsNonEmpty()` before
  serializing. A `serialize()` throw is no longer misreported as
  "Canvas is empty." — distinct toasts for distinct failure modes.

- HIGH: `loadWorkflowsStore` now logs (instead of silently swallowing)
  parse failures on the localStorage fallback, and warns when /userdata
  is reachable but a stale fallback blob still exists from an earlier
  outage — gives the user a recovery breadcrumb.

- HIGH: `loadUserPicks` logs parse failures so a corrupt picks blob is
  visible in the console before the next save overwrites it.

Skipped from the review with rationale:
- multi-tab seed race: sub-second window, both tabs write identical
  defaults, no observable wrong state — navigator.locks not worth it
- splitting `koolook_sidebar.js` and decomposing `renderPanel`:
  refactor scope, deferred to follow-up
- `loadGraphData` rename comparison: agent's claim was wrong; existing
  `wf.filename !== wfName` matches the upstream `filename` shape (bare
  name, no extension) we verified earlier against ComfyUI_frontend
  v1.44.x source

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(sidebar): second-pass review fixes — rollback, idempotency, corrupt-file handling

Addresses the residual findings from the post-625aa97 review pass.

BLOCKING:
- patchCanvasMenu is now idempotent. Adds a __koolookCuratedPatched
  sentinel on LGraphCanvas.prototype so a soft-refresh / re-import
  doesn't stack duplicate "Add to Curated Sidebar" entries on every
  node's right-click menu. (LGraphCanvas is shared with every other
  pack, so non-idempotent prototype mutation was visible globally.)

HIGH:
- persistWorkflowsToServer now returns "server" | "fallback" | false
  instead of just true/false. seedWorkflowDefaultsIfNeeded only marks
  WORKFLOWS_SEEDED_KEY when the seed actually reached /userdata —
  previously a fallback-only seed (when /userdata was unreachable on
  first install) marked the user as "seeded" and they would have an
  empty server file forever after recovery.

- All 8 commit() callsites that mutate workflowsCache (saveAndToast,
  move, archive, restore, rename workflow, delete workflow, rename
  directory, delete directory + delete-non-empty-directory, create
  directory) now go through a new persistMutation helper that:
  - snapshots the cache before mutation
  - bails (toast no-op) if mutate returns false
  - rolls back the cache + re-renders if commit() returns false
  - toasts persistFailedMessage so the user isn't told a write
    succeeded when both /userdata and the localStorage fallback
    rejected it
  Pairs with snapshotCache() helper for explicit snapshot/restore.

- addToMyPicks tri-state ("added" | "duplicate" | "failed") so callers
  can distinguish "already in favorites" from "save failed" on
  localStorage quota / private-mode failure. Both call sites (the +
  toolbar button and the right-click "Add to Curated Sidebar" menu
  entry) updated to handle all three states.

- fetchWorkflowsFromServer now distinguishes a network failure from a
  parse failure of an existing-but-corrupt /userdata file. Returns
  SERVER_FILE_CORRUPT (a Symbol) on parse error; loadWorkflowsStore
  surfaces a toast and returns { corrupt: true }; setup() skips
  seedWorkflowDefaultsIfNeeded entirely so we don't auto-overwrite a
  recoverable-but-unreadable file with stock defaults.

- loadWorkflowOntoCanvas snapshots the current canvas before
  app.loadGraphData and restores it on throw. Previously a workflow
  with a now-unregistered node type could destroy the canvas partway
  through and leave the user with neither the old graph nor the new.

- Stale localStorage fallback detection (when /userdata is reachable
  but a fallback blob from a previous outage is still present) now
  surfaces a toast in addition to the console warning, so a user who
  doesn't have DevTools open gets a recovery breadcrumb.

MEDIUM:
- Modal escape-handler leak fixed via centralized close(). Every
  dismissal path (Escape key, overlay click, Cancel/OK calling
  overlay.remove()) now tears down the document-level keydown listener.
  Existing call sites that use overlay.remove() keep working — the
  remove method itself routes through cleanup.

Skipped from the review with rationale:
- subcategoryFor cosmetic mismatch fallback at L1000-1008 — silent
  rewrite is rare-path defensive, not load-bearing; defer.
- Optional toast on seedDefaultsIfNeeded storage failure — console
  warning is sufficient for a development-time scenario; defer.
- File size / module split (1864 lines) — refactor scope, deferred to
  follow-up per prior commit message.
- Magic sentinel strings ("__new__", action enum strings) — pure
  cleanup; defer.
- dataset.expanded boolean-string compare — pathStates Map already
  carries booleans; cleanup defer.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 16:33:53 +02:00
malkuthroandClaude Opus 4.7 87ecba2f4d Release v0.2.0: Wan 2.2 sequence fix + cinema color surface + alpha output (#43)
Renames the [0.1.10] CHANGELOG section to [0.2.0] (the rolled-forward
work from PR #42 was never published as 0.1.10) and bumps pyproject.toml
to match. Minor bump signals the meaningful surface changes:

- New encode return tuple shape (LATENT, STRING, IMAGE).
- Six new cinema log color spaces + ACES 2065-1 + Rec.2020 Linear.
- Compress (Log) HDR mode with per-profile soft-shoulder + log-domain
  highlight denoise.
- New latent_sampling parameter (sample / mean / mode).
- Behavior change: exposure now applied in linear space for non-Raw
  source spaces (was a code-domain multiplier before).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v0.2.0
2026-05-04 08:42:07 +02:00
malkuthroandClaude Opus 4.7 f24838a6ce feat(vae): sequence dispatch + cinema color surface + alpha output (#42)
Combines two changes to Easy_hdr_VAE_encode/decode (Koolook v2.3.3 fork):

1. Rank-aware dispatch + 5D-output normalization. 5D video tensors paired
   with a 2D image VAE are iterated frame-by-frame on encode (stack on
   dim=2) and per-frame decoded on decode (cat on dim=0). 5D output from
   3D-aware video VAEs (Wan 2.2, Hunyuan, CogVideoX, LTX — exposed via
   `vae.latent_dim == 3`) is reshaped (-1, H, W, C) to ComfyUI's standard
   IMAGE batch, mirroring stock VAEDecode at nodes.py:303-304. Fixes the
   "1056x41 count=1" symptom where 41-frame Wan 2.2 sequences collapsed
   to a single squashed image; SaveImage now writes one PNG per frame.

2. Cinema color surface ported from upstream Radiance v2.3.3 (without the
   broken 4K cosine-blend tile engine that motivated the slim fork in the
   first place). 12 source/target spaces including six cinema log curves
   (ARRI LogC3/LogC4, Sony S-Log3, Panasonic V-Log, DaVinci Intermediate,
   RED Log3G10) plus ACES 2065-1 and Rec.2020 Linear. Compress (Log) HDR
   mode pairs encode-side log-curve encoding with decode-side per-profile
   soft-shoulder + log-domain highlight denoise for HDR-clean roundtrips.
   New latent_sampling parameter (sample/mean/mode). Alpha now surfaces
   as a real third IMAGE return on encode (the previous flag was a no-op).

Exposure is now applied in linear space for all non-Raw source spaces;
Raw source still does a raw-domain multiplication so users who know what
they are feeding the VAE keep their bytes intact.

Helpers extracted to forks/radiance_koolook/versions/v2_3_3/color_helpers.py
(log curves, soft-shoulder, log-denoise, sampling-mode helper, color
matrices, dispatch tables) so nodes_vae.py stays focused on node wiring.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-04 01:41:09 +02:00
malkuthroandClaude Opus 4.7 428d4f538b chore(nodes): drop Easy_Version + bump 0.1.9 (#41)
Per maintainer direction: Easy_Version was a one-line node that turned
an INT N into the string `vNNN` (zero-padded). The first trivial node
ever written for the pack, never used in production workflows. Removed
to keep the pack's surface focused on the higher-value nodes.

Anyone needing the exact behavior can either inline `f"v{N:03d}"`
downstream of an INT primitive in their workflow, or pull a more
general string-format node from KJ Nodes / ComfyUI-Custom-Scripts.

BREAKING for any saved workflow that references the `Easy_Version` ID
(node will fail to load on import). Same migration shape as the v0.1.5
__koolook_v1_0_1 cleanup.

Also rolls the [Unreleased] PR #39 LATENT-dict-wrap fix into [0.1.9]
so it ships in the same release.

Touched:
- Deleted: k_easy_version.py
- __init__.py: removed version_mappings / version_display imports + merges
- README.md: removed node-table row, Pipeline category list update,
  Features bullet, Usage section, Node Code Overview line, install
  category list — Easy Version no longer mentioned anywhere
- pyproject.toml: 0.1.8 -> 0.1.9
- CHANGELOG.md: [0.1.9] entry covering the removal + the carried-over
  PR #39 fix

Net effect: pack drops from 8 nodes to 7. Koolook/Pipeline subfolder
now has only EasyAIPipeline.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
v0.1.10
2026-05-04 00:10:39 +02:00
malkuthroandClaude Opus 4.7 0a914cb8de chore(dev): add scripts/sync_to_dev.py + KOLOOK_COMFYUI_DEV_PATH env var (#40)
For one-line fixes that don't deserve their own version bump, copy the
runtime files into the maintainer's live ComfyUI custom_nodes folder
directly instead of going through the publish-and-update round-trip.

What this adds:
- `scripts/sync_to_dev.py` — minimal Python (no third-party deps); reads
  `KOLOOK_COMFYUI_DEV_PATH` from the environment (auto-loads `.env` at
  the repo root); copies the curated set of runtime-relevant files
  (top-level `k_*.py`, `__init__.py`, `config.json`, `forks/`, `web/`)
  while skipping CI/docs/metadata. Errors out cleanly when the env var
  is unset — never guesses a path.
- `.env.example` entry `KOLOOK_COMFYUI_DEV_PATH=` with explanatory
  comment, matching the existing `KOLOOK_*` convention.
- `CLAUDE.md` section documenting the trigger phrase `dev-sync` so a
  fresh agent session knows to invoke the script when the maintainer
  asks for it.

This is a development-loop convenience only — anything worth shipping
still goes through the normal PR + release flow.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-04 00:02:02 +02:00
malkuthroandClaude Opus 4.7 1098c17f5a fix(vae): wrap Easy_hdr_VAE_encode output in LATENT dict (#39)
vae.encode() returns a raw torch.Tensor, but ComfyUI's LATENT type is
contractually `{"samples": tensor}`. The encoder was returning the bare
tensor, so wiring it into KSampler triggered:

    IndexError: too many indices for tensor of dimension 5

at `latent_image = latent["samples"]` in common_ksampler. Python 3.11+
traceback markers point right at the subscript. With Wan 2.2's 5-D
(B, C, T, H, W) latent, the string-on-tensor index goes through PyTorch's
__getitem__ and surfaces as IndexError rather than TypeError.

Fix matches upstream ComfyUI's VAEEncode pattern:
    return ({"samples": t}, )

The decoder side already destructures `samples["samples"]` correctly,
so it's unaffected.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 23:51:20 +02:00
malkuthroandClaude Opus 4.7 b7a17b570b chore(categories): split Koolook/VFX into Pipeline / Image / VAE + fix VAE display names (#38)
Three small UX fixes from the v0.1.7 post-release inspection:

1. Categories: the catch-all `Koolook/VFX` subfolder is replaced with
   three more granular ones:
   - Easy_Version, EasyAIPipeline   -> Koolook/Pipeline
   - easy_ImageBatch                -> Koolook/Image (joins EasyResize)
   - Easy_hdr_VAE_encode/decode     -> Koolook/VAE

2. Easy_hdr_VAE_encode / Easy_hdr_VAE_decode display names: previously
   the display equalled the node ID (`Easy_hdr_VAE_encode`) so the user
   couldn't find them by typing `koolook` in the node-add search filter
   — they were registered correctly but invisible to the filter. Now:

     "Easy_hdr_VAE_encode" -> "Easy HDR VAE Encode (Koolook)"
     "Easy_hdr_VAE_decode" -> "Easy HDR VAE Decode (Koolook)"

   Matches the title-case " (Koolook)"-suffixed convention used by the
   rest of the pack since v0.1.7.

3. README node tables + categories list updated to match.

Class IDs unchanged everywhere -- saved workflows that reference any of
these IDs continue to load and run unchanged. CATEGORY is purely a UI
hint and only affects the node-add menu hierarchy.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
v0.1.8
2026-05-03 23:45:36 +02:00
malkuthroandClaude Opus 4.7 ee8ccefdf8 docs(reorg) + chore: roll [Unreleased] forward + bump 0.1.7 (#37)
Bundles two changes for v0.1.7:

1. PR #36 (already on main, just rolling [Unreleased] -> [0.1.7]):
   suffix all node display names with " (Koolook)" so they group
   together in ComfyUI's node-add search. Class IDs unchanged.

2. Docs reorg:
   - git mv Glossary.md            -> docs/reference/glossary.md
   - git mv RELEASING.md           -> docs/maintainers/releasing.md
   - Three audience buckets under docs/: user_guide/ (end users),
     reference/ (lookups), maintainers/ (project ops). Each gets a
     short index README.md.
   - Top-level docs/README.md as the navigation entry point.
   - New: docs/maintainers/registry-api.md captures the Comfy Registry
     undocumented version-management API (deprecate/yank/status enum,
     auto-deprecation behavior, curl recipes, optional GH Actions
     workflow YAML for codified mgmt without exposing the token).
   - New: docs/maintainers/node-versioning.md codifies the rules from
     the v0.1.5 PR-review discussion for changing INPUT_TYPES /
     RETURN_TYPES / class names without breaking saved workflows.
   - Imported the existing untracked docs/ images that the maintainer
     started locally (node inventory screenshot, Easy Image Batch
     helper image).
   - Cross-references updated in README.md, CLAUDE.md, the issue
     template, and .claude/skills/add-external-fork/SKILL.md.

Root drops from 6 -> 4 markdown files. The four that stay (README,
LICENSE, CHANGELOG, CLAUDE.md) are the ones that *must* live there for
GitHub / Comfy Registry / tooling / Claude Code reasons.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
v0.1.7
2026-05-03 23:26:46 +02:00
malkuthroandClaude Opus 4.7 9d5cbdabd7 feat(nodes): suffix display names with (Koolook) for discoverability (#36)
Previously typing "koolook" in ComfyUI's node-add search only surfaced
KoolookLoadCameraPosesAbsolute and the v2.3.3 fork VAE nodes (via their
version suffix). The four other nodes (Easy_Version, EasyAIPipeline,
easy_ImageBatch, EasyWan22Prompt) had display names with no Koolook
marker and so were invisible to that search.

Adds " (Koolook)" suffix to each display name, matching the pattern
introduced in #35 for EasyResize_Koolook. Class keys are unchanged
(per CLAUDE.md "Never rename existing namespaced node IDs"), so saved
workflows keep loading and running unchanged.

Also moves Easy_Version's CATEGORY from "VFX/Utils" to "Koolook/VFX"
so it appears under the same parent folder as the rest of the pack.

No version bump — release with the next tag.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 23:17:03 +02:00
malkuthroandClaude Opus 4.7 f57ea38b71 feat(EasyResize): rename to EasyResize_Koolook + proper KJ Nodes attribution (#35)
Resolves the genuine `EasyResize` node-ID collision with
`ComfyUI-EasyFilePaths` (orange-triangle warning #8 in
ComfyUI-Manager). Both packs were registering the bare class name
`EasyResize` — load order determined which one a user actually got.

Changes:

- k_easy_resize.py:
  - Add SPDX-License-Identifier: GPL-3.0-or-later header.
  - Add proper attribution block crediting kijai/ComfyUI-KJNodes
    (Resize Image V2, GPL-3.0) as the original interface inspiration,
    with a per-feature list of how the Koolook implementation
    materially extended the surface (aspect-ratio parser,
    keep_proportion modes, mask + composed outputs, device selection,
    target/original W/H reporting, color-panel passthrough).
  - Register both `EasyResize_Koolook` (canonical, new) AND
    `EasyResize` (deprecated alias) IDs pointing at the same class.
    Saved workflows using the legacy `EasyResize` ID continue to load
    and run; new workflows pick `EasyResize_Koolook` from the search
    and avoid the conflict.
  - Display name on legacy alias reads `Easy Resize (deprecated, use
    'Easy Resize (Koolook)')` to nudge users to switch.

- forks/THIRD_PARTY.md: full attribution entry for KJ Nodes (was
  missing — manifest had it as `license: "unknown"` /
  `sync_state: "needs-upstream-reference"`). License verified as
  GPL-3.0 against upstream `LICENSE` file (same as our pack — no
  relicense pressure).

- forks/forks_manifest.yaml: KJ Nodes entry upgraded from
  `license: "unknown"` to `license: "GPL-3.0"` +
  `license_verified_at`, `status: "inspired_reimplementation"` (more
  accurate than `modified`), and the two exposed_node_ids are now
  recorded explicitly.

- README.md: node table, category list, "Easy Resize" section, and
  Wan-related descriptions all updated to use the new canonical
  `EasyResize_Koolook` ID and display name.

- pyproject.toml: 0.1.5 -> 0.1.6.
- CHANGELOG.md: [0.1.6] entry covering the rename + attribution work.

Follows the node-versioning rules from the `node-versioning` discussion
in PR review: rename via alias, never break saved workflows, document
the deprecation in CHANGELOG.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
v0.1.6
2026-05-03 23:09:04 +02:00
malkuthroandClaude Opus 4.7 60e90ec6c8 Release v0.1.5: registry hygiene + drop unused v1_0_1 Radiance fork (44 nodes → 8) (#34)
* chore: remove maintainer dev workspaces from git tracking + bump 0.1.4

ComfyUI-Manager was reporting "44 nodes / 13 conflicts" for this pack
because the Comfy Registry's static scanner walks every `*.py` in the
published archive and reads `NODE_CLASS_MAPPINGS` dicts — including
the ones inside vendored third-party clones sitting in two folders
that were never meant to be part of the package:

  upscaler_FIX/github_repos/ComfyUI-SuperUltimateVaceTools/
  upscaler_FIX/github_repos/ComfyUI-multigpu/
  nuke_CAM_exporter/wrapper_FIX/wrapper_fun_camera_*-code/
  nuke_CAM_exporter/_Utils-CAM-track/Github-Repos/...

These folders were the maintainer's local debugging / camera-tracking
scratchpads, accidentally committed in Dec 2025 commits ec6a5ff
("Update venv at bX") and 82792e9 ("SuperUltimateVaceTools - fixed
tilling, MultiGPU - fixed torch"). Root `__init__.py` never imported
from them, so ComfyUI runtime loading was unaffected — but the
registry/Manager view was polluted, and we were inadvertently
shipping ~3.7 MB of someone else's GPL/Apache code under our pack
name on every release.

Resolution:
- `git rm -r --cached upscaler_FIX nuke_CAM_exporter` — 70 files
  untracked, still on local disk for the maintainer's reference.
- Added both paths to `.gitignore` so they cannot leak back in via
  `git add .`.
- Bumped pyproject.toml to 0.1.4.

Same class of issue as the v0.1.2 GPL-3.0 relicense (vendored 3rd-party
in MAIN), but lower risk because none of this code was ever imported,
and removing it doesn't change runtime behavior.

Per CHANGELOG note: maintainer should back up the two folders before
pulling this commit into their main worktree, otherwise `git pull`
will apply the deletions to their working tree (history retains them
either way; recoverable via `git checkout HEAD~1 -- ...`).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore(forks): drop v1_0_1 fork entirely + bump 0.1.5

Per user direction (option M from the conversation), removes the entire
forks/radiance_koolook/versions/v1_0_1/ folder — ~5,200 lines, 26
namespaced nodes — that wrapped Radiance v1.0 classes nobody used.

The wrappers were vestigial:
- 22 of them were verbatim copies of upstream Radiance v1.0 classes
  with no Koolook modifications (ImageToFloat32, Float32ColorCorrect,
  HDRExpandDynamicRange, HDRToneMap, ColorSpaceConvert, etc).
- The 1 genuinely-modified wrapper (RadianceOCIOColorTransformV2 with
  multiframe + alpha preservation) was a derivative the Koolook author
  doesn't use.
- The 3 Koolook-original entries (LoadImageEXRSequence, RadianceVAEEncode,
  RadianceVAEDecode) were either unused (LoadImageEXRSequence) or
  superseded in v2_3_3 (the VAE pair → Easy_hdr_VAE_encode/decode).

After this release the package exposes 8 nodes (down from 34):

  Easy_Version
  EasyWan22Prompt
  EasyResize
  EasyAIPipeline
  easy_ImageBatch
  KoolookLoadCameraPosesAbsolute
  Easy_hdr_VAE_encode
  Easy_hdr_VAE_decode

This is BREAKING for anyone who saved workflows using the
__koolook_v1_0_1 namespaced IDs — none of those resolve anymore.
Migration: install upstream Radiance directly for the HDR/EXR/OCIO
functionality, and use Easy_hdr_VAE_encode/decode for VAE work.
Source code recoverable via git history.

Bundled into release/v0.1.4-prep (now v0.1.5) so PR #34 ships the
registry-hygiene fix AND the scope cleanup as one coherent release.

Touched:
- Deleted: forks/radiance_koolook/versions/v1_0_1/ (5 files)
- Updated: forks/radiance_koolook/__init__.py (no more v1_0_1 import)
- Updated: forks/forks_manifest.yaml (v1_0_1 entry marked removed)
- Updated: forks/THIRD_PARTY.md (v1_0_1 entry marked removed + GPL note)
- Updated: README.md (Radiance Koolook nodes section + version layout)
- Updated: CLAUDE.md (Radiance v1 policy → Radiance v2_3_3 policy)
- Updated: pyproject.toml (0.1.4 → 0.1.5)
- Updated: CHANGELOG.md ([0.1.5] entry, [0.1.4] marked superseded)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
v0.1.5
2026-05-03 22:18:24 +02:00
malkuthroandClaude Opus 4.7 5881e8a808 Release v0.1.3: GPL-3.0 relicense, Easy_hdr_VAE_encode/decode (v2.3 fork), license/fork skills (#33)
* chore: relicense to GPL-3.0 and backfill upstream attribution

ComfyUI-Koolook 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 the
whole package is the simplest honest fix and aligns with the dominant
license posture of the ComfyUI custom-node ecosystem.

Changes:

- Add full GPL-3.0 LICENSE text at repo root (pyproject.toml already
  referenced LICENSE but the file did not exist).
- Update README.md "## License" section to declare GPL-3.0 and explain
  the §5(c) implication for downstream users.
- Backfill forks/THIRD_PARTY.md with full attribution entries for the
  Radiance v1.0.1 baseline (already in the repo) and the upcoming
  Radiance v2.3.3 VAE subset (scaffolded in v0.1.2).
- Update forks/forks_manifest.yaml: set the existing entry's license
  field to "GPL-3.0" (was "to_verify_at_source_ref"), add a
  license_verified_at field, and pre-register the v2.3.3 VAE entry
  with verified license metadata.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(skills): add license-pre-check + port add-external-fork to .claude/skills/

Two skills to prevent the v0.1.0/v0.1.1 license-incompatibility class
of bug from happening again on future forks:

- license-pre-check: blocking compatibility audit run BEFORE any code
  is copied or written based on upstream. Reads the upstream LICENSE,
  compares against ours via an SPDX matrix, refuses to proceed on
  incompatible combinations, and records the result for the manifest.

- add-external-fork: ports the existing .cursor/skills/add-external-fork
  procedure to .claude/skills/ so Claude Code can run it directly. The
  port adds a mandatory Phase 0 ("run license-pre-check first") and
  expands Phase 3c to require GPL §5(a) modification headers on
  derived files.

The .cursor/ copy is left in place for Cursor users; the two should be
kept in sync manually until a unifying mechanism is adopted.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* feat(forks): port v2.3 VAE Encode/Decode as slim video-friendly wrapper

Adds forks/radiance_koolook/versions/v2_3_3/ — a slim re-implementation
of RadianceVAEEncode / RadianceVAEDecode under the namespace suffix
__koolook_v2_3_3. Mirrors the input surface of upstream's
RadianceVAE4KEncode / RadianceVAE4KDecode (fxtdstudios/radiance v2.3.3,
GPL-3.0) but deliberately skips the 4K cosine-blend tile engine.

Why skip the tile engine: that pipeline errors with `size of tensor a
(192) must match the size of tensor b (132) at non-singleton dimension
4` when chained into Wan 2.2 video workflows, because the tiler's
spatial layout disagrees with the video VAE's internal temporal-aware
encoding. Modern video VAEs (Wan 2.2, Hunyuan, CogVideoX, LTX) handle
their own internal stitching, so the tiler is redundant and actively
breaks the chain.

What ships:
- forks/radiance_koolook/versions/v2_3_3/nodes_vae.py — RadianceVAEEncode
  and RadianceVAEDecode classes (~330 lines), rank-agnostic, with a
  STRING `debug_info` second return that mirrors the pattern from
  v1's RadianceOCIOColorTransformV2.
- forks/radiance_koolook/versions/v2_3_3/__init__.py — applies
  __koolook_v2_3_3 namespace suffix and " (Koolook v2.3.3)" display
  suffix, mirroring the v1_0_1 wrapper layout.
- forks/radiance_koolook/versions/v2_3_3/UPSTREAM_PIN.yaml — pinned
  upstream commit f262f47 + license metadata.
- forks/radiance_koolook/__init__.py — merges v1_0_1 + v2_3_3 mappings.
- pyproject.toml — version bumped to 0.1.2.
- CHANGELOG.md — full [0.1.2] entry covering the license relicense,
  the new v2_3_3 fork, the two new skills, and the carry-over RELEASING.md
  docs that landed on main since 0.1.1.

Existing __koolook_v1_0_1 nodes are unchanged and remain available for
backward compatibility with saved workflows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* refactor(forks/v2_3_3): rename VAE nodes to Easy_hdr_VAE_encode/decode + bump 0.1.3

Per user direction, the new video-friendly VAE wrappers are now exposed
under clean Koolook-style IDs and display names — no more
`__koolook_v2_3_3` suffix in the ComfyUI node-add UI:

- `Easy_hdr_VAE_encode` (was `RadianceVAEEncode__koolook_v2_3_3`)
- `Easy_hdr_VAE_decode` (was `RadianceVAEDecode__koolook_v2_3_3`)

Why: when both upstream Radiance v2.3.3 and our pack are installed, both
expose `Radiance VAE Encode` in the search — the Koolook-suffixed
display name still buried "Koolook" at the end of a long line, so users
were likely to mis-pick. The new names lead with the Koolook brand and
the Easy_* family convention used elsewhere in this pack
(Easy_Version, EasyAIPipeline, EasyResize, etc.).

Mechanism: new `SKIP_VERSION_SUFFIX = {...}` set in
`versions/v2_3_3/__init__.py` opts specific IDs out of the auto-suffix.
The version is still tracked structurally (file path) and textually
(file header, UPSTREAM_PIN.yaml, forks/THIRD_PARTY.md). Future
Koolook-original nodes in this fork can opt out the same way; ports of
upstream-named classes still get the suffix by default for collision
safety.

0.1.2 was test-published to the registry with the previous IDs but
never tagged or formally released; bumping to 0.1.3 publishes the
renamed version cleanly. Old 0.1.2 entry retained in CHANGELOG with a
"superseded" note for transparency.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
v0.1.3
2026-05-03 21:40:41 +02:00
malkuthroandClaude Opus 4.7 3ad73aac3a docs: add RELEASING.md as canonical release procedure (#32)
The v0.1.1 release surfaced two latent gaps:

1. The v0.1.0 tag was on an orphaned merge commit (PR #26 was
   re-merged), so `git describe --tags` failed and ComfyUI-Manager
   showed the installed version as "unknown".
2. `[tool.comfy] PublisherId` was the placeholder "CristianP", which
   does not exist on the Comfy Registry. Every `Publish to Comfy
   registry` workflow run since at least Feb 2026 failed with the
   misleading `400 "Failed to validate token"`.

Both were the consequence of cutting releases ad-hoc. RELEASING.md
codifies the procedure so future releases follow the same path:
- pre-release validation
- release branch + PR with version bump and CHANGELOG entry
- registry publish validated via workflow_dispatch BEFORE merge
  (catches PublisherId / token / version-conflict errors in PR review)
- squash-merge (single commit per release prevents the orphan-tag class)
- annotated tag at the squash-merge SHA
- GitHub Release from the matching CHANGELOG section
- post-release verification (Manager UI + registry status)

Also updates the per-release issue template to mirror RELEASING.md,
and adds links from README and CLAUDE.md so both humans and agents
land on the canonical doc.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 17:13:59 +02:00
malkuthroandClaude Opus 4.7 e8a61e5270 Release v0.1.1: re-tag at current main HEAD (#31)
The v0.1.0 tag points to an orphaned merge commit (PR #26 was
re-merged at f7cedf0, leaving the tagged commit 38433a9 outside
main's ancestry). As a result `git describe --tags` failed locally
and ComfyUI-Manager displayed the installed version as "unknown".

Bump pyproject.toml to 0.1.1 and add a matching CHANGELOG entry so
a fresh annotated tag at the current main HEAD restores
version visibility for installs.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
v0.1.1
2026-05-03 17:05:11 +02:00
malkuthroandClaude Opus 4.7 7121094780 ci: add lint + bandit security gate, wire main protections (#29) (#30)
* ci: add lint, syntax-smoke, and bandit security gate

Two gating jobs for PRs to main: ruff + python -m compileall ("Lint & Smoke")
and bandit static analysis ("Security Gate"). Bandit excludes vendored sub-trees
(forks/, upscaler_FIX/, nuke_CAM_exporter/).

Pattern lifted from malkuthro/ForgeFlow's CI. Status checks will be wired into
the Protect-main ruleset after first green run on main.

Refs #29

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* ci: exclude vendored sub-trees from lint and smoke

Ruff was flagging 93 errors in upscaler_FIX/github_repos/ComfyUI-multigpu/
(vendored upstream copies). Match bandit's exclude list so lint only checks
this repo's own code.

De-vendoring is tracked separately in #28.

Refs #29

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* chore: remove unused imports, add ruff config

- Drop unused os/json from k_easy_version.py and torch from k_easy_wan22_prompt.py.
- Add [tool.ruff] in pyproject.toml: extend-exclude vendored sub-trees,
  per-file-ignores F841 for k_easy_resize.py (intentional symmetry vars
  pad_b/pad_r/original_B/original_C are kept for clarity).
- CI workflow simplifies to plain `ruff check .` since config is now centralized.

Refs #29

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-03 16:31:18 +02:00
malkuthro 8fc28d1d57 chore: fix .gitignore syntax and exclude pycache/pyo/DS_Store.
Replace ".venv/*" (wrong syntax — only ignores files inside) with
".venv/" so the directory itself is ignored. Add __pycache__/, *.pyc,
*.pyo, and .DS_Store which were missing and previously got tracked.

Companion to a history rewrite that strips .venv/, __pycache__/,
*.pyc, ACES_workflow/, _ComfyUI_workflows/, and _ComfyUI_settings/
from past commits (these trees were already removed from the working
tree in earlier releases but bloated the repo to 235MB; post-rewrite
size ~24MB).

Made-with: Cursor
2026-05-01 11:08:50 +02:00
malkuthro 9069560379 docs+chore: README sync, sibling-projects pattern, skill, and path-leak cleanup (#27)
Squash of 4 commits from docs/readme-sync-current-nodes. Out of scope (tracked in #28): de-vendor upstream copies under upscaler_FIX/ and nuke_CAM_exporter/.
2026-05-01 10:59:42 +02:00
malkuthro f7cedf06ed Merge pull request #26 from malkuthro/release/v0.1.0-prep
Release/v0.1.0 prep
2026-04-24 16:21:33 +02:00
malkuthro c529a1f947 Prepare v0.1.0 release metadata and node naming.
Set project version to 0.1.0, align changelog entries for the new fork/versioning baseline, and include the compact OCIO node ID override for the v1_0_1 Radiance set.

Made-with: Cursor
2026-04-24 16:10:31 +02:00
malkuthro 52712f05b6 Align release references and define clean tagging policy.
Remove stale links to deleted docs, document forward tagging conventions for repo/fork/version namespaces, and keep release checklist aligned with current workflow.

Made-with: Cursor
2026-04-24 15:56:30 +02:00
malkuthro 7f8eda4809 Restructure fork tracking and versioned Radiance layout.
Consolidate fork metadata into forks/, move Radiance custom code into versioned folders, and align external reference naming so upgrades and cross-machine parity are easier to manage.

Made-with: Cursor
2026-04-24 15:45:04 +02:00
malkuthro 52c9a22daa Merge pull request #25 from malkuthro/kflame-public-cleanup-runpod-move-v2
Cleanup v2: remove RunPod management scaffold from public repo
2026-02-15 16:12:34 +01:00
kflame99 7c34d902f4 Address cleanup feedback: ignore RunPod re-additions and tidy changelog 2026-02-15 15:10:57 +00:00
kflame99 075ef223e8 Remove RunPod scaffold from public repo after private migration 2026-02-15 14:54:53 +00:00
malkuthro 2d77433a2a Merge pull request #23 from malkuthro/kflame-runpod-jupyter-zip-pathguard-v6
RunPod: Hires recipe path fix for diffusion_models
2026-02-15 15:14:03 +01:00
kflame99 dab9e2f29b Use diffusion_models path for Wan2.2 checkpoint in hires recipe 2026-02-15 14:11:01 +00:00
kflame99 9a379052fe Add Hires-FIX-upscaler_v01 recipe with Wan2.2 model and Lightx2v LoRA 2026-02-15 14:08:00 +00:00
malkuthro 99fb21056e Merge pull request #21 from malkuthro/kflame-runpod-jupyter-zip-pathguard-v4
RunPod: security/runtime hardening follow-up
2026-02-15 15:01:26 +01:00
kflame99 92ee906451 Harden Jupyter startup token handling and runtime checks 2026-02-15 13:56:14 +00:00
kflame99 f3f87eba1d Address PR feedback: lock parsing safety, token auth, and path refs 2026-02-15 13:19:58 +00:00
kflame99 9cf45aa38b Validate ComfyUI-relative paths in recipe compiler 2026-02-15 13:12:15 +00:00
kflame99 68e26a636a Enable Jupyter and file tools by default with recipe flags 2026-02-15 12:37:59 +00:00
malkuthro 984d37fac1 Merge pull request #17 from malkuthro/kflame-runpod-3zone-structure-v7
RunPod v7: address review blockers + finalize 3-zone layout
2026-02-15 12:32:37 +01:00
kflame99 73e25e668d Address review: restore registry files and fix lock/docs/tooling sync 2026-02-15 11:28:04 +00:00
kflame99 682de29220 Add context headers to moved technical reference docs 2026-02-15 11:17:00 +00:00
kflame99 36903bbded Simplify setup area and relocate deploy/technical docs 2026-02-15 11:09:16 +00:00
kflame99 b1296c2c6c Reorganize Runpod_Comfy into setup/image/core layout 2026-02-15 10:57:47 +00:00
kflame99 91a6abb178 Refactor to admin_recipes with per-recipe builds and compiler flags 2026-02-15 10:31:16 +00:00
kflame99 11c9dbd06e Add helper to create per-image profile sets 2026-02-15 10:13:15 +00:00