f98633faedd5ccbc2a98d53dc72622fac61dfd78
174
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f98633faed | fix(sidebar): keep one context menu open (#112) | ||
|
|
972accfd22 |
docs(guide): onboarding guide overhaul — visuals, layout, and copy (#110)
* docs(guide): refine overview, mental-model diagram, cards, and recipes - Sidebar overview: replace screenshot with post-#108 redesign, re-tune marker percentages and grid layout, bring markers flush to image edge, rewrite Nodes/Workflows action-bar callouts. - Snapshot mental-model SVG: drop the U-shaped return arrow, restyle boxes with ForgeFlow accent strips, show actual on-disk file paths (Client_Shot010.json + Client_Shot010_autosave/periodic.json), rename Project library to Custom Folder, replace dramatic caption with a single-line equation-style summary of manual vs auto-save behaviour. - Function cards: convert chip spans to bullet lists, replace "Safety / setup" with "Smart organization" (folders/drag-drop/move), tighten Search description, "Group" -> "Smart view" on Nodes chip. - Recipes: hide focus-dot overlays, restore wrapper panel with darker #111111 fill, strip all styling from images, scale screenshots to 70%, right-align callouts to screenshot edge, split callout text into separate paragraphs, pin figcaption to bottom. - Crop save-snapshot.png and load-snapshot.png to remove backdrop padding. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(guide): refreshed screenshots, recipe layout overhaul, marker re-tuning Sidebar overview - Replace sidebar-overview.png with the taller 666x1202 redesign so the screenshot fills more of the column height alongside the callouts. - Re-measure section dividers (y=76/152/262/342/418/1202) and re-tune the six marker percentages to match the new image with ~0.4% inset inside each row band. Recipes - Replace all-icons-new.png, search-B.png, search-C.png with the redesigned screenshots showing post-#108 letter-glyph buttons. - Recipe layout: outer wrapper restored with #111111 fill (darker than the screenshots' edge color), screenshots scaled to 70% with no inner border or shadow, callout right-aligned to the screenshot's right edge (margin-right: 15%), figcaption pinned to bottom via flex margin-top: auto, screenshot+callout pair vertically centered above the figcaption. - Add folder-actions.png as a new "Smart organization" recipe with callout text covering create/delete/drag-drop. - Add right-click-functions.png replacing the workflow-search recipe; callout describes the right-click action menu on saved workflows. - Drop the autosave-recovery.svg in favor of the all-icons recipe; the recovery topic is already covered upstream in the Snapshot file box. - Workflow save-selection callout rewritten with two paragraphs (Save / Save selection) and explains the 'Module' tag. - Load snapshot callout split into two paragraphs and shortened. - Search-C callout broadened to "Search narrows displayed items so useful nodes and workflows are easier to find." - Re-enable .focus-module overlay as a translucent yellow rectangle anchored bottom-right of the all-icons screenshot, highlighting the two workflow action buttons. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(guide): remove orphan image assets - web/guide/img/autosave-recovery.svg — replaced upstream by the folder-actions recipe; the SVG is no longer referenced from index.html. - web/guide/img/screenshots/search-B.png — replaced upstream by right-click-functions.png in the workflow-actions recipe. Tree now contains only the 9 image assets actually used by the guide (2 SVGs + 7 PNG screenshots). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(guide): add Two-layer save band, dedupe Snapshot band header - New band "Two-layer save" sits between the snapshot mental-model band and the simple-workflow section. Explains in prose: named save vs background autosave, the <snapshot>_autosave/ recovery copy, the YES/NO load-prompt when autosave is newer, and pre-load recovery files for reversible Loads. - Style the new band with .band-text + scoped <code> styling for inline path fragments (subtle tinted bg, monospace). - Snapshot mental-model band: replace h2 ("Snapshot = your project kit") with the SVG's previous inner header ("Snapshot saves the whole project kit.") since the two were redundant. Drop the inner header from the SVG and shift boxes/arrows/caption up to absorb the freed space (SVG height 460 -> 320). - Drop max-width: 760px on .band > p so the band's intro paragraph spans full panel width. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ea4ee071e7 |
feat(snapshot): YES/NO modal on newer auto-save + 24h timestamps (#111)
* feat(snapshot): autosave UX — YES/NO modal when newer + 24h local timestamps Two snapshot-UX threads in one slice: 1. **24-hour timestamps with relative-recent tier** in the Load dialog and snapshot status tooltip. The previous `en-US` 12-hour format produced "12:56 AM" for timestamps just past midnight, which read visually like noon. Now: `just now` / `12 min ago` / `today 14:32` / `yesterday 23:58` / `May 6 14:32`. Centralized in a new `format_time.js` helper so future surfaces stay aligned. 2. **YES/NO choice when an auto-save is newer than the saved version.** Click any preset row in Snapshot → Load and, if the matching `<preset>_autosave/periodic.json` is more recent than the named save's mtime, the confirm dialog upgrades from "Load preset" to a YES/NO question with both timestamps shown side-by-side. YES loads the auto-save, NO loads the older deliberate save (the row clicked), Esc cancels. Standard pre-load auto-save protection still runs on either path. Server-side `/koolook/presets/list` augments root rows with `latestAutosaveMtime` via one extra `stat()` per row — no file read, cheap on large libraries. Aligns the system with the user's mental model: named saves stay deliberate (only updated on Save / Quick Save), and the auto-save is treated as a recovery copy the system surfaces at decision time rather than a parallel save the user has to remember to check. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(snapshot): restore-from-auto-save baselines as "auto-saved", not "saved" `doAutosaveRestore` was calling `markStateSaved()` after a successful restore, which baselined the *named-save* fingerprint to the auto-save's content. The dot turned green even though `<preset>.json` on disk still held the OLDER deliberate save — blurring the deliberate-save model the Path C UX deliberately reinforces (named saves only change on Save / Quick Save). New `markStateAutosaved()` export in `snapshot.js` baselines the *periodic* fingerprint instead, so the dot reads "auto-saved" (blue) until the user explicitly commits the restored state via Quick Save or Save. Threaded through `tree.js` → `showLoadSnapshotDialog` via the existing dependency-injection pattern. Toast message also updated to nudge: `Restored auto-save of "Foo" — Quick Save to commit to the named file.` Fixes the regression flagged on the PR #111 review. Applies to BOTH restore call sites (the new YES path AND the long-existing Recovery disclosure) since they share the helper. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
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 |
||
|
|
7545d1dba1 |
fix+refactor(sidebar): Insert toast surfaces both miss types + Create-directory toolbar tidy (#106)
* refactor(sidebar): move Create-directory to right-click on Workflows header The 📂 (pi-folder-open) button used to live in the Workflows action bar between the section label and the Save buttons. Tree-structure operations (create / rename / delete a folder) already live on right-click of the row representing the structure being mutated — but top-level dir creation was the lone outlier in the toolbar. Move it to right-click on the "Workflows" header row so the action bar contains only save actions (Save canvas, Save selection) and every tree-structure operation lives where the others do. Implemented via a new optional `rootContextMenu` field on section descriptors plumbed through `buildFolder`; only the Workflows section opts in. Fresh-install path is unchanged — the Save modal's "+ New directory…" entry still creates the first directory when the workflow store is empty. Closes #104. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sidebar): Insert toast surfaces pack misses + subgraph misses when both present `insertWorkflowOntoCanvas`'s pre-flight error path had two failure modes behind one "missing type" classifier: pack-misses (registered_node_types has no entry → install the pack) and subgraph-misses (UUID-typed wrapper whose definition wasn't registered → native Load registers as a side effect). The "all-misses-are-subgraph" early-return only fired when EVERY missing type was a UUID; mixed cases fell through to a single "install the pack(s)" message that listed UUIDs alongside pack types but silently dropped any mention of subgraph registration. User experience: install pack, retry Insert, hit a second unexplained "still missing" failure on the unregistered subgraph. Branch the toast three ways — pure-pack / pure-subgraph / mixed — and make the mixed case name both fixes in one shot ("install pack(s) + right-click → Load once after install to register the subgraph"). The pure-subgraph message also tightened to mention the right-click → Load gesture explicitly, since the previous "Load the workflow once" was ambiguous between left-click (which Inserts on modules) and right-click. Console.warn now logs `{ packMisses, subgraphMisses }` separately for diagnostics rather than the lumped `missingTypes` array. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sidebar): de-jargon Insert toast — talk in gestures, not "subgraph definition not registered" User feedback: "I got that message. I didn't understand why I can't insert it with a left click." The previous wording — "subgraph definition not registered" — was technically accurate but unhelpful. A ComfyUI user wants to know what to *do*, not what's broken under the hood. Toasts now talk in gestures and outcomes: - Pure-subgraph: "Can't Insert "X" yet — it needs to be Loaded once first this session. Right-click → Load it, then left-click Insert will work." - Pure-pack: "Can't Insert "X" — missing node(s): A, B. Install the required pack(s) first, then retry." - Mixed: "Can't Insert "X" yet — two steps: (1) install missing pack(s) for A, B, then (2) right-click → Load it once. After that, left-click Insert will work." The "Load" verb mirrors the actual menu-item label in `workflowRowContextMenu` so the user doesn't hunt for a "paste" or "set up" item that doesn't exist. Engineer-speak (subgraph / registered / definition) stays in code comments + console.warn for devs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sidebar): tighten Insert toast wording — drop the "why", keep the gesture Per maintainer feedback ("Isn't this version a shorter version, better?"), drop the "it needs to be Loaded once first this session" explanatory clause from the pure-subgraph case and the "two steps:" preamble from the mixed case. The user wants to know what to *do*, not why it's broken. If the registration wears off on a fresh session the toast just re-fires and the same gesture solves it again — no need to pre-warn. New wording: - Pure-subgraph: "Can't Insert "X" yet — Right-click → Load it once, then left-click Insert will work." - Pure-pack: "Can't Insert "X" — missing node(s): A, B. Install the pack(s) first, then retry." - Mixed: "Can't Insert "X" yet — (1) install pack(s) for A, B, (2) Right-click → Load it once. Then left-click Insert will work." The "left-click Insert will work" outcome confirmation stays — the user just tried that gesture and got an error, so reassuring them it WILL work after the fix closes the loop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
4f282e7e79 |
feat(snapshot): durability + recovery UX — atomic writes, local time, in-UI fallback recovery (#105)
* fix(snapshot): atomic preset writes + local-timezone tooltips Hardens the snapshot library against mid-write corruption and stops the auto-saved indicator from rendering Central Time on every machine. `POST /koolook/presets/file` now stages writes to `<file>.json.tmp` and `os.replace`s the final name into place — same atomic-rename pattern `_write_settings` already uses. Covers every preset write path through the same route (named, periodic, pre-load, future recovery snapshots). The `.tmp` suffix is invisible in Load and Recovery dialogs because both list endpoints filter to `.json`. `formatCentralTime` is now `formatLocalTime` with the hardcoded `timeZone: "America/Chicago"` removed — the browser formats with the user's system timezone, and `timeZoneName: "short"` renders the matching abbreviation (PDT / EDT / CET / …). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(snapshot): drop timezone abbreviation from local-time tooltip Follow-up to b0f17b9. `timeZoneName: "short"` rendered the user's local timezone abbreviation (`GMT+2`, `CDT`, `PDT`, …) in the auto-saved hover tooltip, but the abbreviation read as clutter — the user already knows their own timezone. Tooltip now shows just the wall-clock time, e.g. `May 7, 2026, 11:43 PM`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(snapshot): recovery section deep-link + save-time visibility What you actually asked for last round, properly delivered this time. The Load dialog's Recovery auto-saves section was a list of file names with no path context, no save time, and no escape hatch to the file system — so power users could see the autosaves existed but couldn't inspect them in Finder, sort by mtime, or pick a specific recovery point. Three additions close that gap: 1. **`📂 Open` button at the top of the Load dialog** opens the snapshot library folder in the OS file manager. Server route `POST /koolook/presets/reveal` shells out to `open` (macOS), `explorer.exe` (Windows), or `xdg-open` (Linux), with path-traversal grounded at the library base via `_resolve_target`. 2. **`📂 Open` button on each `<preset>_autosave` group header** deep- links into that specific subfolder, so the user lands directly on the autosave files for one preset. 3. **Per-row save time** — recovery rows now show `saved May 7, 2:34 PM` from the file's mtime instead of the `exported <date>` that hid the time-of-day. The bare filename replaces the redundant `Periodic auto-save · <subdir> · <iso>` displayName so the timestamp is visible at narrow modal widths. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(snapshot): offline-fallback banner gets Restore + Discard actions The "Offline workflow data found" critical toast used to be a one-way street — Copy details + Dismiss, but nothing ever cleared the localStorage key, so the banner re-fired on every page load forever. Two recovery actions close the loop: - **Restore as snapshot** — wraps the offline workflows into a full snapshot envelope (live picks bundled in so a later Load wouldn't wipe them) and writes it as `recovered-<iso>.json` into the snapshot library via the now-atomic `writePreset`. Clears the localStorage key on success → banner self-extinguishes. The user lands on a durable, in-UI-visible artifact. - **Discard offline copy** — `showConfirmModal` confirm-then-clear. Same end state for the banner; explicit user-acknowledged data loss. Mechanics: - `criticalToast` extended to accept `actions: [{label, onClick, primary?, busyLabel?}]`. Existing callers (`{ copyText }` only) are unchanged. - Toast firing moved out of `loadWorkflowsStore()` and into `koolook_sidebar.js` (entry point) where the action handlers can reach `writePreset` / `loadUserPicks` / `showConfirmModal` without creating circular imports. - `clearOfflineFallback()` exported from `workflows_store.js` for the localStorage `removeItem` step. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(snapshot): unify Load dialog header and Recovery group layout Three visual-consistency fixes so the library section and each `<preset>_autosave` group header read like the same UI control instead of two slightly-different ones. 1. **Saved-time on every row.** `loadPreview` now threads `mtime` from the listing endpoint into the preview object, and `formatPreviewMeta` prefers it over the snapshot's self-reported `exportedAt`. Regular snapshot rows now show `saved May 7, 11:53 PM` — same format the recovery rows already used. Falls back to `exported <date>` when mtime isn't available. 2. **Recovery group headers get the full path.** Subdir name on top, `<library>/<subdir>` underneath — the same CSS pair (`folder-name` + `folder-path`) the library section uses. Reuses `renderLibraryLocation` with a new `title` option so the autosave header skips the "Library folder:" prefix while inheriting all styling. 3. **Library Open button matches the group Open button.** Was `koolook-modal-button` (larger), now `koolook-snapshot-row-btn` (smaller, same as group headers). Same flex options on both rows (`flex-start` alignment, `space-between` justification, `gap: 8px`). `formatAutosaveMeta` collapsed back into `formatPreviewMeta` since the formats converged. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(snapshot): single disclosure arrow + identical row layouts Two visual fixes after the previous round still left the Recovery section feeling different from the Snapshots section. - Drop the literal `▸` / `▾` characters from the recovery summary — the native `<details>` element already renders a disclosure triangle, so the previous text duplicated it. User saw two arrows side-by-side. - Wrap each group's rows in `.koolook-snapshot-list` so they get the same bordered-box treatment around them that the library snapshot rows have. Strip the outer border + visuals from `.koolook-recovery-list` (now just a scroll container) and `.koolook-recovery-group-header` (now just padding) so the inner classes drive appearance — same `folder-name` + `folder-path` pair + `koolook-snapshot-row-btn` Open button + `koolook-snapshot-list` rows wrapper as the library section. Both surfaces should now read as the same UI control. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Revert "chore(snapshot): single disclosure arrow + identical row layouts" This reverts commit 82874c3b3ada977c236c03e57479a29126ee68e7. * Reapply "chore(snapshot): single disclosure arrow + identical row layouts" This reverts commit 76e52bdf7cce5c6e84e883f8714921e2f506a9b3. * fix(sidebar): remove backticks from CSS comments inside template literal The comments I added in 82874c3 contained literal backticks (around identifiers like `koolook-snapshot-list`) — fine in CSS, FATAL inside a JS template literal. The CSS string in `ensureStyle()` is a backtick-delimited template, so an inner backtick prematurely closed the template, the rest of constants.js parsed as broken JS, the module failed to load, and every sidebar file that imports from constants.js collapsed with it. Net effect: entire Kforge Labs sidebar disappeared in the standalone app. Why pre-commit `node --check` missed it: that command defaults to script mode for `.js` files and tolerates this edge case. The browser loads our files as ES modules (stricter), and would have flagged the backtick. Verified the fix with `node --check` on a `.mjs` copy of every sidebar file before committing. Comments rewritten without backticks. Added an in-CSS-comment warning calling out the constraint so future changes don't regress. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(snapshot): tempfile-based atomic write + onCancel for showConfirmModal Addresses two review findings on PR #105. P1: The previous atomic-write used `<file>.json.tmp` — a fixed, predictable name. An attacker (or accidental user) with write access to a shared library could plant that name as a symlink pointing outside the library, and `Path.write_bytes` would FOLLOW the symlink past the path-traversal guard. Replaced with `tempfile.mkstemp`, which (1) opens with `O_CREAT | O_EXCL` semantics so any existing dirent (including a symlink) at the target name aborts the open, and (2) generates a randomized name so an attacker can't pre-plant anything there. The temp file still lives inside the validated target_dir and `os.replace` still does the atomic swap onto the final name. P2: `showConfirmModal` had no cancel callback. The recovery toast's "Discard offline copy" handler wrapped it in a `new Promise(resolve => …)` that only resolved on confirm, so cancelling left the toast button stuck in `"Discarding…"`. Added `onCancel` parameter; the recovery toast now resolves `{ dismiss: false }` on cancel, re-enabling the button. Also closes the second half of #84 — `dropPlaceholdersForPacks` in canvas_io.js had the same Promise-leak shape and the missing cancel callback was the root cause. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e5e07f6c50 | feat(sidebar): preserve subgraph definitions in modules (#103) | ||
|
|
6f1025f66b | fix(sidebar): preserve module links on insert (#102) | ||
|
|
719a240934 | fix(sidebar): clamp hover preview card (#101) | ||
|
|
7d7bc0d132 |
feat(sidebar): browse snapshot library folders (#99)
* feat(sidebar): browse snapshot library folders * fix(sidebar): handle snapshot folder save edge cases * docs(sidebar): clarify browse folder threat model |
||
|
|
a0a8469448 |
feat(sidebar): build-tag footer + theme auto-pull + × removability + pack badges (#98)
Five user-visible changes to the Kforge Labs sidebar that complete the
dev-sync verification loop and fix theme-mode discovery:
- Build-tag footer at the bottom of the sidebar: `dev <sha> · <time>`
on line 1 (SHA at 13px monospace, the at-a-glance identifier),
italic <scope> on line 2. Reads `web/_dev_build.json` written by
`scripts/sync_to_dev.py --scope "<short scope>"`. Visible-in-app
companion to PR #95's chat-report convention — closes the gap when
the maintainer is juggling parallel sessions and can't tell whether
the browser tab is showing the latest sync. Absent on registry
installs; footer renders empty there.
- Theme/category mode now mirrors repo-mode population: REPOS auto-
pull union user-picks, deduped, with the new auto-pull-hidden set
filtered out. The earlier picks-only design dropped the entire
Koolook pack from theme mode on a stock install, even though those
same nodes appeared in repo mode — silently violating the "my
favorites in two views" mental model. `gatherNodesByTheme` rewritten
to seed from REPOS first, then user picks, with the same
autoCategoryRoots + hidden filtering repo mode uses.
- `×` works on every favorite, including auto-pulled REPOS nodes.
Adds `koolook.autoPullHidden.v1` localStorage Set; `×` on an auto-
pulled row calls `hideAutoPullType(type)` so the next render skips
it. `addToMyPicks` calls `unhideAutoPullType` as a side effect so
re-adding via `+` automatically restores visibility. Same uniform
"remove / bring back" UX whether the node was a user pick or REPOS
auto-pull.
- Pack-name badges restored on theme-mode leaves only. Small dim
`Koolook` / `KJNodes` text on the right of each row, same opacity
as the search-flatten breadcrumb. Memory aid for "where did this
node come from?" Repo mode and search-flatten still skip the badge
(origin is conveyed by tree nesting / breadcrumb prefix).
- `scripts/sync_to_dev.py --scope` flag: extends PR #95's brief-
output design with a JSON artifact (`<target>/web/_dev_build.json`)
carrying commit + timestamp + worktree + scope, which the sidebar
footer fetches on render. Doesn't change the existing one-line
chat-report contract or the `_build_line()` shape.
Tested via dev-sync against the live ComfyUI install:
- Footer renders correctly (`dev
|
||
|
|
afe539e8dc |
chore(dev-sync): brief output + build-id stamp + chat-report convention (#95)
* chore(dev-sync): brief output + build-id stamp + chat-report convention
After a dev-sync the maintainer was running 4 parallel ComfyUI installs
with no way to tell which version was just synced and a 50-word "what's
new" message that was hard to skim. Two fixes, one PR.
Script (`scripts/sync_to_dev.py`):
• Default output is now ONE line — the per-file "copied: <path>" lines
move behind `-v` / `--verbose`. Useful when something goes wrong; not
useful for the routine "did it work" signal.
• New summary line includes a build identifier:
synced 10 entries @ <short-sha> v<version> -> <target>
Both the SHA (via `git rev-parse --short HEAD`) and version (parsed
from pyproject.toml) are best-effort — either can be missing and the
line still reads cleanly.
• Dry-run picks up the same shape:
would sync 10 entries @ <short-sha> v<version> -> <target>
CLAUDE.md (project instructions):
• Tightened the `dev-sync` section to its essentials.
• Added a "Chat report format" subsection — the agent must include the
build line from script output and a ≤10-word summary naming what to
LOOK AT after restart, not a list of changes:
dev-sync @ <short-sha> v<version> — <≤10 word summary>. Restart.
• Concrete examples included so the convention is unambiguous.
No runtime behavior change — pure tooling + docs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(dev-sync): two-line report format — sha + worktree, then summary
Maintainer feedback: the previous format ("dev-sync @ <sha> v<version>
— <summary>. Restart.") cluttered slot 1 with version info and forced
the summary onto the same line. Both made the message harder to scan
when juggling multiple parallel ComfyUI installs.
New format — two lines, build id alone on line 1:
<short-sha> - <worktree-name>
<≤10 word summary of what to look at>
Worktree name disambiguates "which checkout fed this sync" when the
maintainer is running several in parallel — the version field rarely
moves between syncs so it bought less than the worktree name does.
Script changes:
• Replaced `_build_id()` with `_build_line()` returning the
pre-formatted ``<sha> - <worktree>`` header.
• Replaced `_get_version()` with `_get_worktree_name()` (REPO_ROOT
basename — works for both worktrees and the main checkout).
• Output now prints the header line first, then the mechanical
"synced N entries -> <target>" line, then the restart hint. Eye
lands on the identifier before the details.
CLAUDE.md change: matching update to the chat-report convention,
including two examples.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(dev-sync): drop "restart ComfyUI" line + user-initiated-only policy
Maintainer feedback:
• The "restart ComfyUI to pick up changes" line at the end of the
script output is noise — they know to restart, the reminder
clutters the message. Removed.
• Dev-sync was being run by sessions automatically (post-commit, on
ship-pr, etc.), silently overwriting code that other parallel
sessions were reviewing. Now explicitly user-initiated only:
project CLAUDE.md spells out the policy and lists the automated
flows that must NOT trigger it; the script docstring carries the
same warning so anything that reads it sees the rule.
Chat report convention: line 2 is now described as "scope of what to
review" (the change in this build) rather than "thing to look at after
restart", to match how the maintainer actually thinks about it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
8dfb966be0 |
feat(sidebar): snapshot durability — per-preset autosave subfolders, Quick Save, status indicator, sticky persist toasts (#94)
* feat(sidebar): snapshot durability — pre-load + periodic auto-saves, sticky persist toasts, Save-dialog library path
Preventive measures after a state-loss incident: a Load erased work, with no
visible warning and no recovery path. Four changes:
1. **Save dialog library path.** Both the input modal ("Save as new…") and
the three-way overwrite dialog now show a tail-truncated breadcrumb
("…/user/koolook-presets2/") with the full path in the tooltip — the user
sees where the save will land before clicking. New
`formatLibraryPathBreadcrumb` helper + `.koolook-modal-pathline` CSS;
threaded through `getLibraryInfo` (already wired for Load).
2. **Pre-load auto-save.** Every destructive Snapshot Load writes
`_autosave_pre_load_<iso>.json` to the library BEFORE `applySnapshot`. On
auto-save failure the Load is aborted entirely with a sticky critical
toast — the whole premise is "Load never destroys state without a
recovery point." Pre-load autosaves don't rotate; every Load is a
permanent recovery file.
3. **Periodic auto-save.** Every 5 minutes a defensive snapshot is written
to `_autosave_periodic_<iso>.json` IF state changed (string-equality
fingerprint on picks+workflows, exportedAt stripped). Skipped when tab
is hidden. Rotates: keeps last 5. First tick at +30s so load/seed
settle first. Started from sidebar setup() after registration.
4. **Critical (sticky) toasts.** New `criticalToast(msg, {copyText})`
variant — no auto-dismiss; optional "Copy details" button hands the
user unsaved JSON for manual recovery. Wired into:
* `commit()` fallback-only path — warns "saved to browser-local
fallback only" once per outage, re-arms when /userdata recovers
* `persistMutation` full-failure path — replaces auto-dismiss toast,
includes the unsaved JSON via Copy
* `loadWorkflowsStore` corrupt-file path — sticky so "your data
looks lost" can't be missed
* `loadWorkflowsStore` stale-fallback-detected path — was a 4.5s
toast, now sticky with Copy of the fallback JSON; turns evening-
work-in-localStorage recovery into an explicit user action
No API or storage changes outside the snapshot library. Existing user
snapshots untouched. New `_autosave_*` files are valid snapshots and
appear in the Load list (sorted to the top via leading underscore) —
deletable via the regular row × button.
Files: 6 changed, +433 / -10.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(sidebar): snapshot status indicator — current preset + saved/auto-saved/unsaved dot
Replaces the static "Snapshot" label in the action row with a dynamic
status display:
● <preset name> · <state>
Dot color encodes status:
• green — saved (live state matches the last named save)
• blue — auto-saved (matches the latest periodic auto-save only)
• orange — unsaved (diverged from any save / auto-save)
• grey — none (no preset tracked)
Tooltip carries the long-form explanation including the latest auto-save
ISO timestamp.
State machine:
• markStateSaved() baselines the fingerprint after a successful named
save (writeAndToast) or successful Load (applySnapshot).
• Periodic auto-save updates _lastPeriodicFingerprint + _lastPeriodicAt
AFTER a successful tick — flips "unsaved" to "auto-saved" without a
mutation event.
• setCurrentPresetName fires SNAPSHOT_STATUS_CHANGED_EVENT so the
indicator name refreshes when Load/Save updates the tracker.
Persistence:
• Saved fingerprint is mirrored to localStorage
("koolook.snapshot.savedFingerprint.v1") so the saved/unsaved
distinction survives page reloads. Without this, every reload would
show "saved" until the user mutated something — defeating the point.
• At session start, if the tracker has a name AND no persisted
fingerprint exists, we baseline once (the "first-ever session" edge).
Imperfect for "user closed mid-edit" but the alternative (always
show unsaved on first load) is worse UX.
Fingerprint = JSON.stringify({picks, workflows}). Same shape used by
periodic auto-save's change detection — single source of truth, no
risk of "saved" and "auto-saved" disagreeing about what the state is.
Files: 5 changed, +237 / -21.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(sidebar): per-preset autosave subfolders + Quick Save button
Reorganizes auto-save storage to keep the user's library tidy and add a
fast-path save action.
NEW LAYOUT:
<library>/
Wan video kit.json ← user-named save (Load list)
Wan video kit_autosave/ ← shadow folder (HIDDEN from Load)
periodic.json ← single rolling file, overwritten
pre_load_<iso>.json × ≤5 ← rotates, permanent per Load
another preset.json
_unsaved_autosave/ ← used when no preset is tracked
periodic.json
pre_load_<iso>.json × ≤5
WHY:
The previous layout dropped every auto-save as a flat `_autosave_*.json`
file at the library root. Two problems:
1. Cluttered the user's Load dialog (sorted to top via leading
underscore but still visible).
2. Filesystem view (Total Commander, Finder) showed dozens of these
files alongside the user's named saves — visually noisy.
The per-preset subfolder layout makes the on-disk structure self-
documenting: "the shadow recovery for X lives at X_autosave/". Hidden
from the Load list at the server (filters subdirs + legacy `_autosave_*`
flat files), so the regular Load flow only enumerates user-named presets.
WHY one rolling `periodic.json` (not timestamped):
The latest periodic auto-save IS the most useful periodic state. Earlier
periodic snapshots are worse copies of approximately the same work.
Trades disk diversity for cleanliness — pre-load files cover the
"recovery from a deliberate destructive action" case independently.
WHY timestamped pre-load files (rotated, keep 5):
Each Load is a deliberate destructive action and deserves a permanent
recovery point. Rotation bounds disk; rare "5 Loads back" recovery loss
is the trade-off for not letting the folder grow unbounded.
QUICK SAVE BUTTON:
New `pi pi-save` icon button between Load and Save. When the user has a
preset loaded/saved and wants to save again on top of the same name,
they don't need the dialog with confirmation. One click, no prompt,
toast confirms ("Quick-saved 'X'."). Disabled (greyed out) when no
preset is tracked — the regular Save button remains the entry point
for naming a new preset.
Refreshes its enabled state on `SNAPSHOT_STATUS_CHANGED_EVENT` so that
loading a preset, saving as new, or clearing the tracker via partial-
load failures all flip the button state correctly.
SERVER (koolook_routes.py):
• New `_DIRNAME_RE` validator (same charset as filenames, no extension)
• New `_resolve_target(lib_base, subdir, name)` — symlink-escape check
grounded at LIBRARY base (not subdir base) so a hostile symlink
inside an auto-save subfolder can't redirect writes outside.
• GET / POST / DELETE / HEAD `/koolook/presets/file` accept optional
`?dir=<subdir>` query param. Server auto-creates the subdir on POST.
• GET `/koolook/presets/list` accepts optional `?dir=` for autosave-
subfolder enumeration (used by the JS pruner). Without `dir`, hides
subdirs AND legacy `_autosave_*` flat files at root so the Load
dialog only sees user-named presets.
JS (snapshot.js):
• `writePreset` / `readPreset` / `deletePreset` / `presetExists` /
`listPresets` accept `{dir}` option, propagated to fileQuery.
• `_autosaveSubdir()` resolves `<presetname>_autosave` or
`_unsaved_autosave` from the current preset tracker.
• `writePreLoadAutosave` writes to subfolder, returns
`<subdir>/<filename>` token; new `_prunePreLoadAutosaves`.
• Periodic timer overwrites `<subdir>/periodic.json` (single file)
instead of rotating timestamped files.
• Removed the global flat-file `_autosave_*` constants.
JS (tree.js):
• New Quick Save button between Load and Save in the snapshot row.
• `refreshQuickSaveDisabled` toggles the button on every status event.
BACKWARDS COMPATIBILITY:
Existing users with `_autosave_*` files at the library root from the
previous layout will see them stop being written to (the new code
writes to subfolders only). The server-side list filter hides them
from the Load dialog so they don't visually pollute, but the files
themselves remain on disk for manual recovery / cleanup.
Files: 3 changed, +277 / -96.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(sidebar): address review — recovery autosave UI + subtitle on overwrite-confirm
Two review findings on PR #94:
[P2] Pre-load autosaves were written into per-preset subfolders but
the Load dialog only listed root-level user-named presets, so
recovery was unreachable from the UI.
[P3] The Save-as-new flow's existing-name → "Overwrite?" confirm
dialog dropped the library breadcrumb (showConfirmModal lacked
subtitle support, even though showInputModal and
showThreeWayDialog had it).
P2 — RECOVERY AUTO-SAVES IN THE LOAD DIALOG:
Added a new server endpoint `/koolook/presets/autosaves/list` that walks
every `*_autosave/` subdir (including `_unsaved_autosave/`) and returns
a flat list of recovery files with their subdir, name, mtime, and size.
Why a dedicated endpoint? The user-facing list MUST hide subdirs to keep
the Load list clean; the recovery path is the explicit "show me autosave
folders" API, separated cleanly so its boundary is grep-able.
JS gets a matching `listAutosaves()` helper that fans out preview reads
to extract displayName + counts (N+1 cost is real but bounded — typical
user has ~10-30 autosave files, paid only when the recovery section is
expanded).
The Load dialog gains a collapsible `<details>` section ("▸ Recovery
auto-saves") below the regular preset list. Lazy-loaded on first expand.
Items are grouped by subdir so users can scan visually for the preset
they want to recover from. Each row shows a kind badge (PRE-LOAD orange
/ PERIODIC blue / OTHER), the snapshot's display name, and the standard
preview meta (pick + workflow counts + exportedAt).
Click → confirm modal → optional pre-recovery autosave (recursive
protection — restoring an autosave is itself a destructive Load) →
applySnapshot. Post-restore, the tracker is set to the original named
preset derived from the subdir (`Foo_autosave` → "Foo"), or null for
`_unsaved_autosave`. State indicator flips to "saved" via markStateSaved.
P3 — SUBTITLE ON CONFIRM MODAL:
`showConfirmModal` gains an optional `subtitle` parameter (string or
HTMLElement), same contract as showInputModal and showThreeWayDialog.
The Save-as-new flow's overwrite-confirm now passes `buildPathSubtitle()`
so the user keeps library context across the input → exists-check →
confirm chain. Important when Settings → libraryPath has been changed
mid-session, or facility users juggle multiple shared libraries.
Files: 5 changed, +355 / -1.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
d763b36adc |
chore(legal): add NOTICE + SPDX headers across web/ (#93)
Codify attribution before the sidebar gets more visibility. NOTICE is the single human-readable source of truth — copyright statement, plain-English attribution request with a copy-pasteable credit line, pointer to LICENSE and forks/THIRD_PARTY.md so it shows the project respects others' attribution too. SPDX-FileCopyrightText / SPDX-License-Identifier headers added at the top of every web/*.js (entry point + sidebar/ + ai_pipeline) so license scanners (GitHub, FOSSA, reuse-tool) parse each file correctly and stripped-attribution forks become an obviously deliberate act rather than "I forgot to copy a file." GPL-3.0-only matches the LICENSE file (no "or later" wording in it). Year range 2025-2026 spans first commit (2025-09-28) to today. Comment-only change — no runtime impact. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
75b7e76f11 |
feat(sidebar): node-preview fidelity — read nodeData, 5-col grid, widget pills (#78) (#82)
Two changes that ship together because the visual rework was masking the real bug: 1. `readSlots` (and the title/category/description reads in `renderPreviewCard`) were looking at static `nodeClass.INPUT_TYPES` / `RETURN_TYPES` properties, which **don't exist** on ComfyUI-registered nodes — ComfyUI parks the original V1 def at `nodeClass.nodeData` (set by `litegraphService.ts`'s `registerNodeDef` right before `LiteGraph.registerNodeType`). Every card was rendering as just header-and-badge because the slot arrays came back empty for every ComfyUI node. Reading from `nodeData` first (with the old property names as a legacy fallback for any non-ComfyUI registered nodes) fixes the empty-card class. 2. Visual rework to look like a real node mock instead of a generic info card. Mirrors upstream NodePreview.vue more faithfully: flat header row (small colored dot + display name) over a red PREVIEW badge; slot rows in a 5-column grid (`[dot] [input-name] [spacer] [output-name] [dot]`) so inputs and outputs line up horizontally as parallel sockets; widgets rendered as individual rounded pills with `◀ name [spacer] value ▶` chrome to mimic LiteGraph widget UI. Closes #78 Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d021e8c8da |
feat(sidebar): theme-classifier rework + Nodes-row polish (#80)
* revert(sidebar): drop drag-link-release menu priority patch The patch in `web/sidebar/connection_menu.js` was reorder-only — it sorted existing menu items so curated picks floated to the top. In practice ComfyUI's drag-link-release menu has its own pre-filter that surfaces only ~4 stock nodes per socket type (e.g. for an IMAGE-compatible drop target: VAEEncode, VAEEncodeForInpaint, SaveImage, PreviewImage). Most user picks never appear in that filtered list, so the reorder had nothing to hoist and the feature was invisible in practice. A useful version of this would have to ADD picks to the menu — walk the user's curated picks, type-match each against the dragged socket, inject matches as new menu items — rather than just reorder. That's a larger patch and out of scope for tonight; filing as a follow-up rather than shipping dead code. Removed: `web/sidebar/connection_menu.js`, the `patchConnectionMenu` import + call in `web/koolook_sidebar.js`, and the corresponding [Unreleased] CHANGELOG entry. Kept: spotlight-on-add, modal drag-out fix, and the `pinExpanded` buildFolder fix from #76 — all independently useful. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(sidebar): theme-classifier rework + Nodes-row polish Replaces the previous "category mode" (canonical-segment first-segment grouping, which produced a fragmented tree of `image` / `images` / `Image-Save` / `KJNodes/image` / `Koolook/Image` for the same semantic theme) with a broad keyword classifier that folds anything mentioning an image-related word into a single "Image" bucket regardless of source pack or category casing. Picks-only; flat list inside each theme; falls back to pack-prefix-stripped first segment when no classifier matches. Five coordinated changes shipped together: 1. Theme classifier (new). `THEME_CLASSIFIERS` is an ordered list of 13 broad themes (Image / Video / Mask / Audio / Model / Sampler / Conditioning / Text & Prompt / Math & Logic / Pipeline & Batch / I/O / Camera / Utility) with regex pattern arrays. `extractTheme` builds a search corpus from packLabel + category + display title + type ID and walks the classifiers in order — first match wins. First match for "Image Comparer (rgthree)" is "Image" (not Math) because Image is earlier in the list. Symbols touched in tree.js: `gatherNodesByTheme`, `extractTheme`, `classifyTheme`, `THEME_CLASSIFIERS`, `emitThemeChildren`, `collectFlatFromThemes`, `countNodesInThemes`. Replaces `gatherNodesByCategory`, `emitCategoryTreeChildren`, `collectFlatFromCategoryTree`, `countNodesInCategoryNode`, `ensureSyntheticBucket`. 2. Pack-name badges removed. `makeNodeLeafRow` no longer accepts a `packBadge` parameter; the badge rendering block is gone; all callers (3 sites) updated; CSS rule `.koolook-pack-badge` removed from constants.js. The breadcrumb prefix on search-flatten rows already conveys origin in a cleaner form, and theme mode by definition doesn't care about source pack. 3. Mode-toggle icons swapped. Repo mode: `pi-database` -> `pi-sitemap` (now actually tree-shaped). Theme mode: `pi-sitemap` -> `pi-bars` (sandwich). Better icon-to-meaning mapping. 4. Default mode flipped to "category" (theme). Newcomers more often think "where are my image nodes" than "which pack is this from." Existing users with `koolook.groupMode.v1` already in localStorage keep their saved choice. 5. Layout polish. `+` button uses a `pi pi-plus` icon at 12px (matches toolbar icon-button height) with a new dusty-green accent class `koolook-add-btn-green` (rgba 120/165/100). New divider between the search field and the Nodes action bar. `koolook-sidebar` gained `padding-top: 10px` so the Snapshot row no longer hugs the panel top edge. CHANGELOG [Unreleased] updated with Changed entries for items 1+2. Note: a CSS comment containing backticks (`koolook-icon-btn`) inside a JS template literal silently broke module load on the first attempt — backticks closed the template early, leaving "koolook-icon-btn" as a stray identifier the browser rejected with "Unexpected identifier 'koolook'". Fixed by removing the backticks from the comment. `node --check` did not catch this; the only reliable check is browser-side. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(sidebar): address fresh-eyes review nits on theme classifier Three NITs + one IMPORTANT comment from the PR #80 review pass: - IMPORTANT: Add a code comment on `\bget\b` / `\bset\b` in the Utility classifier warning future editors not to generalize to unbounded `/get/` / `/set/`. Current narrow forms are intentional — they're the last-resort net for trivial getter/setter nodes that didn't hit any earlier classifier. - NIT: Fix the `classifyTheme` doc-comment example for "Image Comparer" to be technically accurate. The previous wording claimed Math's `/\bcompare\b/i` would match "Comparer" — it wouldn't, because the trailing word boundary fails on "Comparer". The actual reason it lands in Image is the `/image/i` substring hit on the "Image" word in the corpus, not ordering. - NIT: Drop `.toLowerCase()` on the search corpus in `extractTheme`. Every classifier pattern carries the `/i` flag, so the explicit case fold was redundant work. - NIT: Remove dead `(nc && ...)` defensive checks after the early return on `!nc`. Per CLAUDE.md "no defensive coding for impossible cases" — `nc` is guaranteed non-null on the lines below the early return. Same in `gatherNodesByTheme` where `nc` is fetched after `extractTheme` already filtered out unresolved picks; added a comment noting the invariant. Behavior unchanged. All pure correctness/style polish. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
215616f546 |
revert(sidebar): drop drag-link-release menu priority patch (#79)
The patch in `web/sidebar/connection_menu.js` was reorder-only — it sorted existing menu items so curated picks floated to the top. In practice ComfyUI's drag-link-release menu has its own pre-filter that surfaces only ~4 stock nodes per socket type (e.g. for an IMAGE-compatible drop target: VAEEncode, VAEEncodeForInpaint, SaveImage, PreviewImage). Most user picks never appear in that filtered list, so the reorder had nothing to hoist and the feature was invisible in practice. A useful version of this would have to ADD picks to the menu — walk the user's curated picks, type-match each against the dragged socket, inject matches as new menu items — rather than just reorder. That's a larger patch and out of scope for tonight; filing as a follow-up rather than shipping dead code. Removed: `web/sidebar/connection_menu.js`, the `patchConnectionMenu` import + call in `web/koolook_sidebar.js`, and the corresponding [Unreleased] CHANGELOG entry. Kept: spotlight-on-add, modal drag-out fix, and the `pinExpanded` buildFolder fix from #76 — all independently useful. 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> |
||
|
|
ced6492257 |
feat(sidebar): category mode + flatten-on-search + hover preview (#74)
* feat(sidebar): category mode + flatten-on-search + hover preview (#73) Three navigation/discoverability improvements to the Kforge Labs sidebar's Nodes section, shipped together: 1. Category-first grouping mode. Segmented toggle in the Nodes action row (📦 Repository / 🌐 Category) re-groups picks by their node-class CATEGORY path instead of by repo, with canonical-key normalization (`lower().replace(/[\s_-]+/g, "")`) so `Loaders`/`loaders` and `Image/Upscaling`/`image/upscaling` collapse into one folder. Synthetic `(unresolved)` and `(uncategorized)` buckets surface picks whose pack isn't loaded or whose class has no CATEGORY. Pack-name badge on category-mode leaves preserves the source signal. Mode persists in localStorage with cross-tab sync. 2. Flatten-on-search. When the search field is non-empty the Nodes section drops its tree structure and renders a flat list of matching leaves with `Cat › Subcat › Display` breadcrumb prefixes. Workflows and Tags sections retain their tree-under-filter behavior — the change is scoped to Nodes only. 3. Hover preview card. Mirrors ComfyUI's official NodePreview.vue: plain HTML/CSS card with a colored title bar (HSL hue from CATEGORY hash), two columns of type-colored slot dots (reads `app.canvas.default_connection_color_byType` first, falls back to LiteGraph's static palette), widgets with truncated defaults, optional description. 250ms hover debounce, viewport-flip, max-height cap, pointer-events: none, leak-proof teardown wired into renderTree. "Pack not loaded" stub for unresolved picks. New module `web/sidebar/node_preview.js` holds the engine; one `attachHoverPreview(row, type)` call inside `makeNodeLeafRow` covers every leaf-emit site (repo tree, category tree, flat search). Closes #73 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sidebar): preview review-fixes — log INPUT_TYPES errors, teardown on tab/blur, type-guard slot colors Three small additive fixes from the multi-agent review on PR #74: 1. console.warn when INPUT_TYPES() throws so a broken pack is visible during debugging instead of silently rendering an empty-looking node. Matches the project-wide convention (picks_store.js, canvas_io.js, installer.js). 2. visibilitychange + window.blur listeners that call teardownPreview. Closes the leak path where the user navigates away mid-hover via keyboard sidebar-tab switch, modal stealing focus, or page hiding — none of which cross a row's mouseleave region, so the reactive teardown never fired and the pointer-events:none card sat on screen undismissable. 3. typeof === "string" guard on both fallback branches in slotColor so a misbehaving custom-node pack injecting a non-string into default_connection_color_byType produces a visible neutral-grey dot instead of a silent transparent dot. The category_grouping.js extraction (MEDIUM finding, file split) is queued as a separate follow-up issue per the "one PR per topic" rule in CLAUDE.md — pure no-behavior refactor, lands cleaner on its own. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
96b07a2c12 |
feat(sidebar): picks priority — drag-link menu, spotlight on add, modal hardening (#76)
* 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> * feat(sidebar): picks priority — drag-link menu, spotlight on add, modal hardening Three coordinated UX features for curated picks plus two underlying bug fixes uncovered while wiring them. 1. Drag-link-release menu hoists curated picks. When a connection is dragged from a socket and released on empty canvas, ComfyUI's compat- filtered suggestion list now sorts items in the user's curated picks above everything else, with a "── from your picks ──" separator before the rest. Detection via the "Add Node" + "Add Reroute" sentinel pair in the menu's options (narrow enough that other ContextMenu uses are untouched). Purely additive — no compatible nodes are removed, only re-ordered. New `web/sidebar/connection_menu.js` exports a `patchConnectionMenu()` registered alongside `patchCanvasMenu()`. 2. Spotlight effect on add. Clicking the toolbar `+` (or the canvas right-click "Add to Kforge Labs") collapses every Nodes-section pack folder, then auto-expands the pack + subcategory the just-saved node lives in — pedagogical aid that helps users learn which pack each node belongs to. Multi-select adds light up every hit pack simultaneously. Duplicate adds get the spotlight too (the educational reminder still applies). New helper `findPackPathForType` mirrors the gather code's REPO-precedence-then-user-pick-fallback. New exported `spotlightAddedPicks(typeNames)` does the collapse + pin sequence. 3. Modal drag-out-of-input no longer dismisses the dialog. Shared modal shell `makeModalShell` gained a mousedown / click-intent check: a click on the overlay only fires dismiss when the gesture STARTED on the overlay too. Drag-selecting text inside an input that releases in the overlay's dark area used to dismiss the modal mid-edit. Affects every Koolook dialog. Snapshot Settings path field also gained a `title` tooltip so the full path is hover-readable. Bug fixes uncovered + carried in this PR: - `pinExpanded` paths now expand on the immediate render. buildFolder's resolution chain gained a `pinnedPaths` check between `forceExpanded` and `pathStates`. Phase 3 of `renderTree` writes pins into pathStates AFTER Phase 2 builds the DOM, so without this check the pin had no effect on the current render. Improves the existing workflow-save pinning (visibly correct now) and is load-bearing for the new spotlight feature. CHANGELOG [Unreleased] updated with all five entries. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.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> |
||
|
|
200311236e |
fix(sidebar): merge preset HEAD route into GET handler (#71)
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> |
||
|
|
93363cc625 |
feat(sidebar): snapshot library v2 — configurable filesystem path via KFORGELABS_PRESETS (#68)
* feat(sidebar): snapshot library — named presets for picks + workflows Implements #46 item 1 (custom save location + cross-machine portability) as a preset library: each preset is one named JSON file under /userdata/koolook-presets/, capturing the full Kforge Labs state. Use cases addressed ------------------- - **Personal portability:** copy `koolook-presets/` between machines, or sync ComfyUI's userdata folder via Dropbox/iCloud/Drive — your kit follows. - **Team / facility shared library:** multiple workstations on the same ComfyUI server share `/userdata/koolook-presets/` natively. One person publishes "facility kit", everyone else loads it. - **One-off transfer (different servers):** Manage tab's Download button saves a snapshot to local disk; Upload imports it into another machine's library. UI -- New top-level **Snapshot** action row above the search field, mirroring the Nodes/Workflows action-row pattern. Three icon buttons (Load / Save / Manage), each opens the same modal at a different starting tab. - Load tab: lists every preset with metadata (workflow count · pick count · export date). Click a row → "Replace current state?" confirm → restores both picks and workflows. - Save tab: input field defaults to `preset YYYY-MM-DD`, fully editable. Existing-name save prompts to overwrite. - Manage tab: per-row Download / Delete buttons + a top-of-tab Upload action (file picker imports a JSON snapshot from disk into the library). Schema ------ ```json { "kind": "koolook-snapshot", "version": 1, "name": "Wan video kit", "exportedAt": "2026-05-05T08:42:13.000Z", "picks": [...], "workflows": { "directories": {...} } } ``` `kind` + `version` for future schema migrations. Anything without `kind === "koolook-snapshot"` is rejected on import. Module shape ------------ - `web/sidebar/snapshot.js` (new) — server I/O on the preset directory, schema validation, gather/apply, download/upload helpers. UI-free. - `web/sidebar/modals.js` — new `showSnapshotModal({initialTab, ...})` that takes capability functions instead of importing snapshot.js directly, so the UI module stays UI-only. - `web/sidebar/workflows_store.js` — new `replaceAllWorkflows(rawStore)` for bulk-replace (bypasses persistMutation since the entire cache is being swapped) + `getAllWorkflowsForExport()` for the gather flow. - `web/sidebar/picks_store.js` — new `setAllPicks(picks)` exported helper. - `web/sidebar/tree.js` — new top action row + 3 toolbar buttons + one wiring helper. - `web/sidebar/constants.js` — CSS for tab strip, list rows, manage buttons. Deliberate scope cuts (per the design discussion) ------------------------------------------------- - No continuous sync. Each save is a deliberate act. - No conflict resolution. Same preset name written from two machines on the same server = last-write-wins. Documented as a known limit; revisit when it bites. - No selective import (preset = full state). Merge can be a v2. - No custom userdata path config. The `koolook-presets/` location is fixed; portability comes from the snapshot files themselves, not from rerouting storage. Verification ------------ - `node --input-type=module --check` on all modified + new JS files — clean. - `python tools/preflight_release.py --skip manager-meta -v` PASS. - Synced to live install on Mac for browser smoke-test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(sidebar): snapshot library now uses configurable filesystem path Reworks PR #67 (which served snapshots from /userdata/koolook-presets/) to expose the library through Koolook's own server-side endpoints, making the storage location configurable via an env var. The userdata namespace is per-install — fine for personal use, useless for the "facility shared library" use case the user explicitly called out. Architectural change -------------------- - The client no longer talks to ComfyUI's `/userdata/*` API for snapshot files. New JS-side endpoints: `/koolook/presets/{info,list,file}`. - Server side: new `koolook_routes.py` registers aiohttp routes on ComfyUI's PromptServer at custom-node load time. Path-traversal protection at the route boundary via a strict filename whitelist regex (`^[A-Za-z0-9 _.()\-]+\.json$`). - Storage location is read from the `KFORGELABS_PRESETS` env var on every request (so re-launching ComfyUI with a new value picks it up without any cache fuss). Default fallback: `<comfyui-userdata>/koolook-presets/`. - Auto-creates the configured directory on first save (parent must already exist — for NFS/SMB mounts the mount must be present). Use cases this enables vs. PR #67 --------------------------------- | Use case | PR #67 | This PR | |-------------------------|--------|---------| | Personal, single-machine | ✓ | ✓ | | Personal cross-machine | symlink-or-sync userdata | point env var at Dropbox folder | | NFS/SMB facility share | ✗ (locked to userdata) | ✓ point env var at mount | | Multiple parallel libs | ✗ | per-instance env var | | Read-only distribution | ✗ | ✓ (server detects, save fails cleanly) | UI difference vs. PR #67 ------------------------ - The Manage tab gains a small `i` icon at the right edge of the upload row. Hover tooltip shows the current library path, source (default vs `KFORGELABS_PRESETS` override), and writability status. No header line, no extra UI weight. Files added/changed ------------------- - `koolook_routes.py` (new, ~180 lines) — aiohttp routes + env-var config + filename validation. Loaded by `__init__.py` at startup; failure is non-fatal (the rest of Koolook still works, just the snapshot feature is unavailable). - `__init__.py` — registers `koolook_routes` import-time side-effect. - `scripts/sync_to_dev.py` — `koolook_routes.py` added to `RUNTIME_PATHS` so dev-sync includes it. - `web/sidebar/snapshot.js` — server I/O section rewritten to call `/koolook/presets/*` instead of `/userdata/koolook-presets/*`. Drops the URL-encoding workaround (we own the route now, so the `name=<file>` query param is clean and the server validates). - `web/sidebar/modals.js` — `showSnapshotModal` accepts a `getLibraryInfo` capability function; the Manage tab calls it and populates the info-icon tooltip. - `web/sidebar/tree.js` — passes `getLibraryInfo` from `snapshot.js` to the modal. - `.env.example` — new `KFORGELABS_PRESETS` block with macOS / Linux / Windows examples for personal-sync, NFS facility share, and SMB facility share patterns. - `CHANGELOG.md` — Added entry rewritten for the env-var design; Internal entry on the new server-side module. Why a separate v2 PR (instead of patching #67) ---------------------------------------------- The userdata-namespace design in #67 was the wrong architecture for what the user actually wanted. Patching it to the env-var design would have meant a near-total rewrite of #67's server I/O paths mid-review. Cleaner: branch fresh from main, cherry-pick #67's UI + stores work as a base, layer the new endpoint design on top. PR #67 will be closed as superseded once this lands. Verification ------------ - `node --input-type=module --check` on every modified JS file — clean. - `python -c "import ast; ast.parse(...)"` on `koolook_routes.py` + `__init__.py` — clean. - `python tools/preflight_release.py --skip manager-meta -v` PASS. - Synced to live install on Mac. User to smoke-test against a real ComfyUI session (the previous URL-encoding 405 should be gone since the new route handlers own the URL space). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(sidebar): redesign snapshot UX — single-click Save, real Settings panel User feedback on #68's first cut: tabbed modal is too complicated, can't see existing presets in the Save flow, the env var alone isn't a real settings UI, the default-path text was stale (still claimed `/userdata/koolook-presets/` after the configurable-path landed). UX changes ---------- - **Save button → one click.** If a preset is currently loaded (tracked in localStorage as `koolook.snapshot.currentPresetName.v1`, survives reloads), prompts "Save over '<name>'?" with three options: Save (overwrite) / Save as new… (rename) / Cancel. If nothing is loaded yet, prompts for a name with `preset YYYY-MM-DD` default, fully editable. - **Load → single dialog.** Preset list with per-row × Delete button. Header line shows the current library path so the user can see where they're loading from. No tabs. - **Cog → real Settings panel.** Single field for the library path (text input, absolute path). Save / Reset to default / Close. Read- only line shows the currently-resolved path + source label (settings panel / env var / built-in default). The "Manage" tab and its Download / Upload sub-actions are gone — the user can already reach the file on disk after a save, no in-app file ops needed. Server side ----------- - New `GET /koolook/presets/settings` returns the saved library path + resolved path + source. - New `POST /koolook/presets/settings` writes the saved library path to `<comfyui-userdata>/koolook-settings.json`. Empty value clears the override. - Resolution chain in `_configured_dir()`: settings file > KFORGELABS_PRESETS env var > built-in default Reads each on every call; no module-load caching. - `info` endpoint gains a `source` field ("settings" | "env" | "default") the UI uses for the readout line. Client side ----------- - `showSnapshotModal` (tabbed) replaced by three exports: showSaveSnapshotDialog, showLoadSnapshotDialog, showSnapshotSettingsDialog. Each is self-contained, no shared state. - `snapshot.js` adds `getSettings`/`saveSettings` for the Settings dialog and `getCurrentPresetName`/`setCurrentPresetName` for the one-click Save tracking. Settings ride on the new server endpoints; current-preset tracking is browser-local. - `applySnapshot` now also updates the current-preset tracker (via the dialog wrapper, not the function itself) on successful load. - `tree.js` toolbar wires three direct callbacks instead of one `openSnapshotModal(initialTab)` helper. Stale text fix -------------- - The Save dialog's hint text in #68's first cut hardcoded "/userdata/koolook-presets/<name>.json" — stale from the userdata- era PR #67. The redesigned Save flow doesn't show that text at all (the path is no longer relevant to the user-typed name); the Load dialog's header line and the Settings dialog's read-only field are the canonical surfaces for "where my presets actually live." Deliberately out of scope ------------------------- - No per-save path picker. The library is one location at a time, set in advance via Settings or env var. Saving to ad-hoc locations would fragment the library and break the "facility shared library" use case. - No in-app Download / Upload (was in #68's first cut). Files are on disk at the configured path; the OS file manager handles transfer. Verification ------------ - `node --input-type=module --check` on every modified JS file — clean. - `python -c "import ast; ast.parse(...)"` on `koolook_routes.py` — clean. - `python tools/preflight_release.py --skip manager-meta -v` PASS. - Synced to live install on Mac. User to smoke-test against a real ComfyUI session. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sidebar): address PR #68 review — atomicity, security, error UX Multi-agent review surfaced 2 BLOCKING + 5 HIGH + 4 MEDIUM + 3 LOW. Validated the BLOCKINGs against the actual code (both real) and applied the bundle of fixes that hold up. Modal-size HIGH (#7 file size) deferred to a follow-up — that's a wider refactor. BLOCKING fixes -------------- - **`applySnapshot` partial-failure tracker gating** (modals.js Load dialog). `setCurrentPresetName(...)` was called unconditionally before checking `{picksOk, workflowsOk}` from the apply. Partial apply (one of picks/workflows persisted, the other didn't) left the tracker pointing at the preset, so the next Save would offer "Save over '<name>'?" and silently overwrite the on-disk preset with the corrupted half-state. Now: tracker is set only on `picksOk && workflowsOk`; partial failure clears it (and the toast tells the user "Reload to recover prior state. Tracker cleared."). - **`replaceAllWorkflows` non-atomic rebind** (workflows_store.js). Rebinds `workflowsCache` before awaiting persist, with no rollback. Now: snapshots the cache via the existing `snapshotCache()` primitive that `persistMutation` uses; on persist failure, restore the prior cache and return false. Load is fully atomic. HIGH fixes ---------- - **Workflows-store invariant block updated.** Lists `replaceAllWorkflows` as the fourth legitimate rebind site (with rollback semantics). "Fifth rebind" warning bumped to "sixth." - **Explicit HEAD route on `/koolook/presets/file`** that just does `is_file()` + status, no body work. The auto-synthesis path was reading the full file from disk just to discard it — wasteful on multi-MB snapshots over Dropbox/iCloud/NFS. - **Client `sanitizeName` mirrors server whitelist regex** (`^[A-Za-z0-9 _.()\-]+$`). Characters outside the whitelist (`+`, `&`, `'`, accented letters, emoji, etc.) collapse to `_` rather than triggering an opaque HTTP 400 at save time. - **Error-reason surfacing via `await resp.text()`.** RFC 7540 strips HTTP/2 reason phrases; behind any HTTP/2-terminating proxy `resp.statusText` is empty. Reading the body always carries aiohttp's `reason=` text. Used by writePreset, deletePreset, saveSettings, readPreset. MEDIUM fixes ------------ - **Symlink follow-through hardening.** `_resolve_within_base` helper computes `(base / name).resolve()` and asserts it's still under `base.resolve()`. Defends against a symlink planted in a facility- shared library dir redirecting reads/writes off-share. - **Atomic settings write.** `_write_settings` writes to `<tmp>` then `os.replace`, so an interrupted process can't truncate the file and silently revert the user's saved library path on next load. - **`__init__.py` import wrapped in try/except.** The "non-fatal" comment promised that an import failure wouldn't break node registration; previously only `install()` returning False was guarded. Now any module-load exception is caught and logged with the `[Koolook]` prefix, and the rest of the plugin loads. LOW housekeeping ---------------- - Dropped dead exports `downloadSnapshotAsFile` + `readSnapshotFile` (left over from an earlier iteration that had Download/Upload UI). - Dropped 5 dead CSS rules (`.koolook-snapshot-tabs*`, `.koolook-snapshot-tab-pane`) from when the modal was tabbed. - `currentPresetName` cleared when the user deletes the currently- loaded preset (Load dialog's promptDelete now checks via the new `getCurrentPresetName` capability). - `loadPreview` warns to console on `kind` mismatch — non-snapshot JSON files in the library directory are now visible in DevTools rather than silently filtered. - `presetExists` is tri-state (`true` / `false` / `null`) — the Save flow refuses to write when the library is unreachable rather than silently skipping the overwrite-confirm prompt. Deferred -------- - modals.js size (1080 lines) — extracting the snapshot dialogs into their own file is a follow-up refactor. Not blocking merge. - Doc drift on maintainer docs / README env-var convention — separate documentation sweep. Verification ------------ - `python -c "import ast; ast.parse(...)"` on all .py files — clean. - `node --input-type=module --check` on every modified JS file — clean. - `python tools/preflight_release.py --skip manager-meta -v` PASS. - `grep -rn "downloadSnapshotAsFile\\|readSnapshotFile\\|koolook-snapshot-tab" web/` — zero hits (dead code gone). - Synced to live install for browser smoke-test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: nudge — re-trigger CI after webhook miss on d6d2bf8 The CI workflow didn't fire on the prior push (Socket Security ran but GitHub Actions Lint/Smoke/Pre-flight/Security Gate didn't). Empty commit to refire the pull_request webhook. * fix(routes): drop unnecessary f-string prefixes flagged by ruff (F541) Two adjacent string literals in the symlink-escape error message had an `f` prefix despite containing no placeholders. CI's ruff check fails on F541 — strip the prefixes so the lint gate passes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b89355ded1 |
feat(sidebar): one-click install of missing nodes via ComfyUI-Manager (#69)
Adds an "Install missing for picks" toolbar button to the Nodes section.
On click it walks the user's curated picks against Manager's
/customnode/getmappings, buckets them into already-installed /
will-install / unresolved, queues each unique git URL through
/customnode/install/git_url, polls /manager/queue/status to drive a
progress bar, and prompts to reboot — collapsing what was previously
a per-node "open node, let Manager prompt, click install, restart"
loop into a single confirm-and-go flow.
New `web/sidebar/installer.js` holds the Manager-API client (detect /
mappings / queue / poll / reboot) plus the picks → installs resolver.
The modal in `web/sidebar/modals.js` drives the four-phase UI
(discovery → confirm → progress → result) and surfaces 403s as
actionable language ("your security level forbids git-URL installs")
rather than raw HTTP. Falls back to a clipboard URL list (with a
`comfy node install` hint) if Manager isn't reachable.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
8ebe0af210 |
Revert "fix(sidebar): avoid 409 on native save when Koolook workflow name collides with an existing native file (#65)" (#66)
This reverts commit
|
||
|
|
13de73a40a |
fix(sidebar): avoid 409 on native save when Koolook workflow name collides with an existing native file (#65)
Symptom
-------
Loading a workflow from the Kforge Labs sidebar whose name matched an
existing native ComfyUI workflow file (e.g. user has both
`user/default/workflows/Hi-Res-FIX_v01.json` AND a Koolook entry
named `Hi-Res-FIX_v01`) and then clicking ComfyUI's native save
button produced a confusing toast:
Error storing user data file 'workflows/Hi-Res-FIX_v01.json':
409 Conflict
The user's only workaround was to rename via Save As.
Root cause
----------
ComfyUI's frontend save flow at
`Comfy-Org/ComfyUI_frontend/src/stores/userFileStore.ts`:
const resp = await api.storeUserData(this.path, this.content, {
overwrite: this.isPersisted,
...
})
`overwrite: this.isPersisted` — passes `overwrite=true` only when
ComfyUI considers the workflow already persisted to disk. Workflows
loaded via the 4th-arg trick on `loadGraphData(..., wfName, {})`
become temporary workflows (`isPersisted = false`), so save POSTs
with `overwrite=false`. The server (correctly) 409s because a real
file already exists at `workflows/<wfName>.json`.
The 409 is **correct ComfyUI behavior** — protecting the user from
overwriting an unrelated file with the same name. ComfyUI's frontend
has no public API for extensions to mark a workflow as
"persisted-from-elsewhere," so we can't make their save flow use
overwrite=true for our loaded workflows.
Fix
---
`loadWorkflowOntoCanvas` now HEADs
`/userdata/workflows/<wfName>.json` before calling `loadGraphData`.
- No collision → tab name is the bare `<wfName>` (existing behavior).
- Collision → tab name becomes `<wfName> (Koolook)`. Save-back
writes `workflows/<wfName> (Koolook).json`, which is a clean
new artifact that doesn't collide with the user's native file.
The load toast surfaces the renaming when it happens, so the user
knows their tab was suffixed and why. When the deployment doesn't
support HEAD (rare), the catch falls through to the bare name —
worst case is the same 409 the user already knows how to work
around.
Verification
------------
- `node --input-type=module --check` on `canvas_io.js` — clean.
- `python tools/preflight_release.py --skip manager-meta -v` PASS.
- Synced to live install on Mac via `scripts/sync_to_dev.py` for
smoke-test against an actual ComfyUI session.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
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 |
||
|
|
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> |
||
|
|
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
|
||
|
|
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> |
||
|
|
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>
|
||
|
|
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
|
||
|
|
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 |
||
|
|
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> |
||
|
|
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 |