f98633faedd5ccbc2a98d53dc72622fac61dfd78
20
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
27705be037 |
feat(sidebar): add onboarding guide (#107)
* feat(sidebar): add onboarding guide * docs(guide): clarify tag search casing * docs(guide): clarify workflow save copy * docs(guide): refine sidebar visuals * docs(guide): refresh save snapshot visual * docs(guide): refresh load snapshot visual * docs(guide): refresh module save visual * docs(guide): use real sidebar screenshot * docs(guide): render sidebar overview screenshot directly * docs(guide): soften sidebar overview annotations * docs(guide): clarify nodes action bar callout * docs(guide): clarify workflow action bar callout * docs(guide): simplify sidebar overview markers * docs(guide): align sidebar overview intro * docs(guide): use real snapshot recipe screenshots * docs(guide): simplify project workflow diagram * docs(guide): make function cards scannable * docs(guide): sketch module recipe icon focus * docs(guide): use real icon screenshot for module recipe * docs(guide): add cropped workflow action screenshot * docs(guide): use full toolbar screenshot for module recipe * docs(guide): use real search screenshot * docs(guide): use real node search screenshot * docs(guide): standardize screenshot recipe cards * docs(guide): tighten recipe callout styling * docs: require visual QA for UI work * docs(guide): address pass-2 review nits |
||
|
|
b185118950 |
feat(sidebar): refresh action icons (#108)
* feat(sidebar): refresh action icons * fix(sidebar): label glyph toolbar buttons |
||
|
|
e5e07f6c50 | feat(sidebar): preserve subgraph definitions in modules (#103) | ||
|
|
6f1025f66b | fix(sidebar): preserve module links on insert (#102) | ||
|
|
413b153702 |
docs(maintainers): refresh stale references + add conventions.md
Audit triggered while debugging the v0.2.0 release surfaced drift in the maintainer docs vs current code: dead curated_defaults.json references (file replaced by starter_preset.json in PR #72), the v1_0_1 fork example (removed in v0.1.5), and a "tooling we don't have yet" entry that's now covered by tools/preflight_release.py. Adds a new conventions.md for cross-cutting rules — currently the Mermaid-for-diagrams convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
cbf2cb4d09 |
chore(ci): drop push-to-main publish trigger — dispatch-only (#91)
The push trigger fired on every release-PR squash-merge to main, attempting to publish a version that workflow_dispatch on the prep branch had already published — producing a 400 "node version already exists" failure and a cosmetic red badge on the merge commit. With the dispatch-from-prep-branch flow now standard (per CLAUDE.md and releasing.md), the push trigger is redundant. Updates docs/maintainers/releasing.md to match: drops the stale "trade-off" callout (whose premise was the auto-publish on merge), replaces the two anti-patterns whose rationale was the auto-trigger with a single "don't forget to dispatch" reminder. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ed6f3dad02 |
feat(sidebar): Modules — splice a saved cluster into the live canvas
Tag any saved workflow with the literal tag `module` and the Kforge Labs sidebar starts treating it as a reusable building block: a green `pi pi-plus-circle` icon replaces the file glyph, left-click **inserts** the cluster at the viewport center (with internal links re-created and the new nodes left selected), and a distinct hover tooltip surfaces the changed semantics. Designed for the `EXR-output stack` / `depth-only render` / `Wan 2.2 prompt module` pattern: small clusters the maintainer wants to drop into many workflows without rebuilding them by hand. What changed: 1. New non-destructive primitive `insertWorkflowOntoCanvas` in `web/sidebar/canvas_io.js`. Pre-flights every referenced node type against `LiteGraph.registered_node_types` and aborts cleanly with a toast naming the missing types — a partial insert with stub nodes is worse than no insert. Then deep-clones the saved graph, calls `node.configure(...)` per node with `node.id = -1` so `app.graph.add()` allocates fresh ids that can never collide with the live canvas, and finally walks the saved `links[]` to recreate internal connections via `originNode.connect(...)` (which auto-allocates fresh link ids — no manual link remap needed). Bbox placement reuses the existing CSS-pixel correction so the cluster lands at the visible viewport center on HiDPI displays. 2. Right-click on any workflow row now offers `Insert into canvas` alongside the existing `Load` — Load remains available for every row regardless of tag, so a module can still be loaded as a full session if the user wants. 3. Convention-driven module classification in `web/sidebar/constants.js` (`MODULE_TAG = module`). Picked a tag instead of a new `isModule` schema field so existing saved entries can be promoted to modules with one right-click → Tags add, no migration needed, and the existing Tags section groups them for free. Tree rendering looks up the tag at row-render time (`isModuleWorkflow` helper) and switches both the leaf icon (green `pi pi-plus-circle`) and the left-click handler (Insert instead of Load). Active rows in the Workflows section AND every row under the Tags section honor the flip; archived rows stay on Load (they're old versions, `review` not `splice`). 4. `showSaveWorkflowModal` gains a `Save as module` checkbox. The selection-save toolbar button passes `defaultModule: true` (the typical `module` candidate is a small cluster the user just selected); the whole-canvas save passes `defaultModule: false`. The tag is added in the SAME `persistMutation` as the entry write (folded into the `mutate` callback in `saveAndToast`), so a commit failure rolls back BOTH the save and the tag together — no `module`-but-untagged ghosts. Docs: new `Modules` section in `docs/maintainers/workflows-sidebar.md` covering the convention, the checkbox flow, per-surface behavior, and how to promote an existing saved workflow to a module without re-saving. Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
a6a31c8086 |
feat(sidebar): Tools row + starter preset replaces curated_defaults (#72)
* fix(sidebar): merge preset HEAD route into GET handler PR #68 shipped koolook_routes.py with @routes.get + @routes.head on the same path. aiohttp's add_get auto-registers HEAD by default, so the explicit @routes.head collided with the auto-synthesized HEAD and crashed ComfyUI startup with "Added route will never be executed, method HEAD is already registered". Trying allow_head=False on the GET to opt out triggered a second crash: ComfyUI's server.py mirrors every PromptServer route into a parallel /api/... table via api_routes.route(method, path)(handler, **kwargs), and RouteTableDef.route's inner closure rejects unknown kwargs with TypeError. So no kwarg-based opt-out is viable. Drop the dedicated HEAD handler. Aiohttp's auto-HEAD-from-GET delivers HEAD probes to get_preset; we branch on request.method to skip the file read for HEAD — preserving the cheap-existence optimization the client's presetExists relies on for multi-MB JSON over Dropbox / iCloud / NFS-mounted libraries. CHANGELOG [Unreleased] updated to match. This bug never reached users — koolook_routes.py was added in #68 (post-v0.2.0) and was never published to Comfy Registry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(sidebar): Tools row + starter preset replaces curated_defaults Two coordinated UX changes for fresh-install distribution + admin ergonomics — designed together and shipped as one unit. 1. New "Tools" row sits between the Snapshot row and the search field. Moves the Export and "Install missing for picks" buttons up out of the Nodes row (admin/advanced operations don't belong in the everyday + Add toolbar). Adds a new "Drop placeholders onto canvas" button — the security_level=normal escape hatch for installing missing packs: instantiates one placeholder per missing pack on a fresh canvas tab so ComfyUI/Manager's standard "Install Missing Custom Nodes" detection takes over via Manager's UI flow (which uses different gates than our programmatic /customnode/install/ git_url call). Nodes row keeps only the everyday + button. 2. curated_defaults.json retired in favor of starter_preset.json. Fresh installs no longer get picks seeded directly into localStorage on first load. Instead the bundled web/starter_preset.json (full snapshot format — picks + workflows + tags + archive) is copied into the user's snapshot library directory as starter.json. The user opens Snapshot -> Load -> starter to apply in one click. Unifies fresh-install distribution with the per-user snapshot library (#68); maintainer flow becomes "build state in ComfyUI -> click the (down-arrow) Tools-row button -> paste over web/starter_preset.json -> commit." Existing users with non-empty picks are explicitly skipped by the new seeder, so their state is untouched. Removed: seedDefaultsIfNeeded, exportPicks, web/curated_defaults.json, SEEDED_KEY, DEFAULTS_URL. Added: seedStarterPresetIfNeeded, exportStarterPreset (snapshot.js), STARTER_SEEDED_KEY, STARTER_URL, STARTER_PRESET_FILENAME, discoverMissingPacks (installer.js), dropPlaceholdersForPacks (canvas_io.js), web/starter_preset.json. Docs: CLAUDE.md "Curated Nodes sidebar — distributed defaults" -> "Kforge Labs sidebar — starter preset distribution". docs/maintainers/curated-sidebar.md rewritten. CHANGELOG.md [Unreleased] updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
7c34d902f4 | Address cleanup feedback: ignore RunPod re-additions and tidy changelog | ||
|
|
075ef223e8 | Remove RunPod scaffold from public repo after private migration | ||
|
|
39fb15f549 | chore: add release templates and expand third-party manifest | ||
|
|
7e159780fe | chore: add release workflow and third-party tracking scaffold |