2af8f080bd0e0fadbb2b3532c77a86ec6231bc87
201
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
2af8f080bd |
Potential fix for code scanning alert no. 1: Prototype-polluting assignment
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com> |
||
|
|
e3f94cca6b | fix(sidebar): reuse draft id for workflow loads (#166) | ||
|
|
96d4b51d7b |
Release v0.3.5 (#160)
* Release v0.3.5 prep * fix(sidebar): bypass stale browser cache for snapshot state * test(sidebar): guard no-store snapshot fetchesv0.3.5 |
||
|
|
092c204060 |
feat(automations): LTX Director iteration-loop framework + 4K investigation docs (#159)
* docs(investigations): kickoff for LTX Director 4K transition breakdown Captures the maintainer's problem statement (HD multi-keyframe works, 4K single-keyframe works, 4K multi-keyframe degenerates into dissolves between keyframes) and an initial conclusion about the WhatDreamsCost LTXDirector node's implementation. Traces the lineage to the Prompt Relay paper (Chen, Huang, Liu — arXiv:2604.10030) via Kijai's ComfyUI port, separates the three mechanisms the node bundles (cross-attention penalty, guide-image keyframes, audio/image preprocessing), and flags the candidates for the 4K dissolve: model-side limit at LTX sampling time vs. node-side length-independent σ in the penalty matrix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(investigations): round 2 — 2K vs 4K + two-stage upscale findings Adds the maintainer's round-2 observations: - 2K renders behave much better than 4K (segments morph instead of dissolving) — model is closer to its training distribution. - The two-stage workflow's upscale stage (stage 2) is overwriting stage 1's audio/animation: lip sync degrades and segment routing weakens. Captures the hypothesis space: - Why 2K > 4K: model prior (out of distribution at 4K) is dominant; token-grid scaling with a length-independent sigma is secondary. - Why stage 2 overwrites stage 1: KSampler denoise re-applies conditioning from scratch; if LTX Director isn't wired into stage 2 the Prompt Relay + audio_attn2 patches go missing; latent shape change between stages forces a penalty rebuild. Includes a 3-option pros/cons table (4K direct vs 2K direct vs 2-stage) and 6 investigation directions ordered cheapest-first, starting with auditing the stage-2 wiring. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(investigations): round 3 — both stages run LTX Director; new prime suspect Maintainer's screenshot confirms both stages route through LTX Director, so round 2's missing-patch hypothesis is ruled out. Audited the stock LTX nodes used in stage 2 against ComfyUI source: - LTXVLatentUpsampler explicitly pop()s the noise_mask and runs a learned (EXPERIMENTAL) neural upscaler — not interpolation. - LTXVConcatAVLatent fills a missing noise_mask with ones_like (denoise fully) for the side that's missing. - LTXVCropGuides removes keyframe slots and clears keyframe_idxs. Traced the audio noise_mask end-to-end through stage 2 and found: audio waveform stays preserved (noise_mask=0), video keyframes stay pinned, but every non-keyframe video frame goes into stage 2's sampler at noise_mask=1 — i.e. fully regenerated at denoise=0.80. That's where stage 1's audio-aligned mouth shapes live, and that's what's getting thrown away. Prime suspect: denoise=0.80 is too high for "preserve stage 1" semantics — most LTX two-stage reference workflows use 0.30–0.40. Secondary suspect: the LTXVLatentUpsampler [BETA] is a learned model that can hallucinate spatially-dense detail (faces, mouths) before stage 2 even starts denoising. Includes a 4-step cheapest-first test plan starting with dropping stage 2 denoise to 0.30. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(investigations): round 4 — scheduler-shape finding + tracking plan Captures two outputs from the planning session so they're preserved for the next pickup: 1. Promotes the maintainer's empirical finding to a locked-in baseline: `linear_quadratic` scheduler at 8 steps produces the right sigma curve shape (flat plateau near sigma=1.0 for ~5 steps, then steep drop in the last 3) for both phases. Any other scheduler degrades fast motion and keyframe transitions. 2. Records the agreed tracking-system plan (BASELINE.md + log.md + per-run screenshot folder, with three 1-5 score axes matching the live trade-off: motion / audio sync / Phase-2 sharpness). Not yet scaffolded — explicit "next step" callout for whoever picks this up. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(investigations): /make-card skill + PNG card renderer toolkit Per-iteration tracking card for the LTX Director 4K investigation. Solves the documentation-drift problem we hit on v01->v02 (multiline summary diverged from actual node values) by treating the saved workflow JSON as the source of truth. Adds: - scripts/make_card.py — reads a ComfyUI workflow JSON, paints a self-contained 540px PNG card via Pillow. Extracts Phase 1 run params (Format/Mult/Scheduler/Steps/Denoise), locked base (Sampler/CFG/Seed/epsilon/Audio src), scene (duration/segments with prompt/audio/keyframe checkboard), free-text BASE notes from `OVERLAY - INFO` Text Multiline, FEEDBACK from `OVERLAY - FEEDBACK` Text Multiline, and a POST-RENDER block that auto-computes render duration from JSON->newest-video mtime delta in the same folder. Layout mirrors the project's snapshot-dialogs design language (panelled sections, sky/amber/ green accents matching docs/designs/snapshot-dialogs.html). - scripts/watch_cards.py — background poller. Watches a working folder; auto-renders <stem>_card.png whenever a workflow JSON appears or is updated. Skips up-to-date PNGs. - .claude/skills/make-card/SKILL.md — /make-card slash command; globs the newest JSON in the working folder, calls make_card.py, shows the PNG inline. - .claude/skills/make-card/work-folder.txt — one-line config of the working folder path. Edit to switch projects. - docs/investigations/experiments/{baseline,combined-card,run-001-card}.html — design prototypes that fed into the PNG layout; kept as history. - docs/investigations/experiments/run-001-card.png and run-v04-card.png — sample card outputs from v03 and v04 of the workflow JSON, included for reference. Issue #151 tracks the eventual native ComfyUI custom node so this flow happens inside the graph; for now the external script + watcher is the loop. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(launch): use absolute Windows python path for design-server The default `python3` runtimeExecutable fails on this Windows host (triggers the Microsoft Store install stub). Hard-coding C:\Python313\python.exe lets the preview MCP and the design-server target work on the maintainer's machine. Worktree-scoped change — main keeps the cross-platform `python3` default. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(automations): docs/loop scaffold + env-var working folder + _AI subdir Promotes the LTX Director card-rendering loop from one-off scripts to a documented, project-portable automation framework. Adds `docs/automations/` as the home for AI-assisted iteration loops: - README.md — the pattern; how to start a loop for a new model - CONVENTIONS.md — cross-cutting: working folder, Text Multiline node contract (Working_Folder_PATH / OVERLAY - INFO / OVERLAY - FEEDBACK), card format, iterations log, skill, watcher, run-id rules, tool locations - LTX-2.3/README.md — entry point for the LTX 2.3 loop; per-iteration steps; full extraction table; active investigation status - LTX-2.3/handoff-checklist.md — 5-minute onboarding for a new machine or new person - LTX-2.3/findings.md — promoted findings (linear_quadratic scheduler locked-in; resolution behaviour; audio mode; open two-stage suspects) Replaces the committed `.claude/skills/make-card/work-folder.txt` with an env-var-driven path. Follows the project's existing `KOLOOK_*` convention — `.env.example` documents the new `KOLOOK_AUTOMATIONS_WORK_DIR`. Real path lives in `.env` (gitignored). No more leaking client-specific absolute paths into committed files. `scripts/make_card.py`: - Loads `.env` automatically; worktree-aware (walks the .git common-dir marker to find the main repo when run from a worktree). - Output folder priority: Working_Folder_PATH node in JSON → env var → JSON's parent dir. - Run with no args: auto-discovers newest *.json in the working folder. - Writes `_AI/card.png` + `_AI/iterations.md` inside the working folder, not the root. Lets the maintainer wipe AI artifacts or user content (JSONs/MP4s) independently. `/make-card` skill simplified to a single command — `python scripts/make_card.py` — the script handles env loading and discovery itself. baseline.html: stripped the project-specific label. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(automations): sync all path references to _AI/ + add layout diagram Audit pass after introducing _AI/ subfolder. Updated every doc and script that still referenced root-level card.png / iterations.md: - docs/automations/CONVENTIONS.md (3 spots) - docs/automations/LTX-2.3/README.md (per-iteration steps + composite) - docs/automations/LTX-2.3/handoff-checklist.md (4 spots, including the off-boarding section which conflated repo commits with working- folder artifacts) - docs/automations/LTX-2.3/findings.md (promotion criterion) - scripts/watch_cards.py (target PNG path now matches make_card.py's _AI/card.png — was writing to a stale <stem>_card.png location) Adds a clear "Where things live" section to docs/automations/README.md showing both layouts side-by-side: - the repo tree (where the tooling, skill, env-example, and per-model loop docs sit) - the per-project working-folder tree (user content at root, _AI/ subfolder for managed artifacts) So anyone picking this up can answer "what's in the repo vs. what's in my project folder" without reading prose. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(automations): add CHEATSHEET.md — one-page dense reference Compact reminder card for returning users. Structured by application context: ComfyUI · Scripts · Folder structure · Conventions · Quick-fail diagnostics. Skippable for first-timers; targets the maintainer who already knows the loop and just needs the right node names, score syntax, and command list at a glance. Linked from docs/automations/README.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(automations): add Bootstrap section to CHEATSHEET — bare-bones setup What's needed to get a fresh machine running the loop, separated from the daily-loop reference. Three checklists: - Repo side: Python + Pillow + .env config - ComfyUI side: was-node-suite + the three required Text Multiline nodes, with exact titles - First card: /make-card → expect _AI/card.png Deliberately skips model-specific setup (checkpoints, LoRAs, per-model custom nodes) — those live in each model's handoff-checklist.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(automations): skip *loop* files in auto-discovery + watcher The maintainer's workflow includes a "loop" stage in ComfyUI that composites the card next to the video and writes the result back into the working folder as `<base>_loop_<seq>.{mp4,json}`. Without a skip rule, auto-discovery treats this composited-card output as the newest input — the script then re-renders the card from yesterday's metadata. Now: any JSON or video file with `loop` in its stem (case-insensitive) is ignored by: - scripts/make_card.py — auto-discover newest JSON, newest video - scripts/watch_cards.py — polling glob Explicit invocation still works: `python scripts/make_card.py <wf_loop.json>` will render a card for that file specifically. Documents the rule in docs/automations/CONVENTIONS.md (new §7) and adds a one-line bullet to CHEATSHEET conventions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * style(automations): satisfy Ruff lint — split if-on-one-line, rename l, drop os CI's Lint & Smoke step flagged 17 violations across the new scripts. Cleaned up: - scripts/make_card.py: 11 × E701 (multi-statement lines) split every `if cond: stmt` onto two lines. - scripts/make_card.py: 5 × E741 (ambiguous variable `l`) renamed to `link` in the two link-dict comprehensions and their downstream lookups. - scripts/watch_cards.py: 1 × F401 (unused `os` import). No functional change — `make_card.py` re-renders the working folder's card identically post-cleanup. Verified `ruff check` returns "All checks passed!" on both files. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(automations): render time from ComfyUI log, not disk timestamps Disk mtimes don't reliably bracket the model run — the maintainer's workflow saves the workflow JSON manually after rendering, assembles QuickTime from EXRs separately, and ComfyUI's metadata bundle writes JSON + MP4 at the same instant. None of those give us actual render duration. Render time now sourced from ComfyUI's own log (`<comfyui-root>/user/comfyui.log`): - Find the latest `saving images: 100%` line → pair it with the next `Prompt executed in X` → that's the most recent EXR-producing render wall-time. - Supports both `Prompt executed in 18.21 seconds` and `Prompt executed in HH:MM:SS` log formats. - Skips trivial follow-up prompts (e.g. audio-VAE load between render and /make-card invocation). Log path resolution: 1. `KOLOOK_COMFYUI_LOG` env var (explicit override). 2. Inferred from existing `KOLOOK_COMFYUI_DEV_PATH` → `<comfyui-root>/user/comfyui.log`. Fallbacks remain in priority order: EXR sequence span (oldest to newest in the latest version subfolder) → MP4 mtime delta → "pending". Docs: - .env.example: documents the optional KOLOOK_COMFYUI_LOG. - CONVENTIONS.md: new §4 "Render duration" describing the resolution chain; section numbers renumbered. - CHEATSHEET.md: updated the "Render time —" diagnostic. Lint: ruff 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> |
||
|
|
1c1c870439 | fix(sidebar): keep loaded workflows untitled in Comfy (#157) | ||
|
|
b42bd4af77 |
chore(python): add bootstrap scripts + pip-audit + line-ending policy (#158)
Solidify the Python test-env path so any agent (Claude / Codex) or
human can bootstrap with one command, and so the global preflight
skill's pip-audit branch actually runs against this repo.
scripts/bootstrap_test_env.{ps1,sh}: one-liner per platform, idempotent
(no-op if .venv exists, --force / -Force to recreate), upgrades pip,
installs project + test extras.
pyproject.toml: add pip-audit>=2.7 to [project.optional-dependencies]
test so /preflight's Python audit branch lights up.
CLAUDE.md, Hard rules: replace the inline venv-bootstrap command with
a reference to the platform script.
AGENTS.md: short "Test Environment Bootstrap" pointer (mirrors CLAUDE.md
per the Claude/Codex symmetry rule).
.gitattributes: enforce LF for *.sh (otherwise Windows CRLF breaks the
bash shebang on POSIX) and CRLF for *.ps1 (Windows convention).
Triggered by the warmup-skill Python detection added in agent-playbooks
PR #34 — projects now need a clean bootstrap path for the new check to
have a recommended remediation.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
4409d3c9d8 |
feat(ai_pipeline): no_subfolders toggle + path normalisation + tooltips (#156)
* feat(ai_pipeline): no_subfolders toggle + path normalisation + tooltips
Three EasyAIPipeline improvements with a 49-case test suite pinning
the behavior so the user-reported "phantom undefined/ folder" bug
can't regress.
no_subfolders (default off): write directly into base_directory_path
with no shot_name/ai_method/vNNN subfolders appended. Base folder is
still auto-created if missing.
Paste-input hardening: _normalize_base_path strips surrounding
whitespace, one matched pair of quotes ("..." from Explorer's Copy
as path), and trailing path separators. Drive roots (C:\, n:/) are
preserved. Plus a belt-and-suspenders rstrip on the final
output_directory — fixes the symptom where a trailing backslash on
the user's pasted base path caused downstream save nodes to split
on /, stringify the empty tail as "undefined", and write into
.../undefined/... on disk.
Smart filename: empty pieces of shot_name / ai_method / vNNN are
filtered before the underscore join, so blank ai_method yields
RTX-upscale.exr (not RTX-upscale_.exr). Same rule for subfolder
build.
Tooltips on every input explain interactions (e.g. ai_method
becomes both filename segment and subfolder; enable_overwrite only
blocks an existing file, not directory).
JS preview helpers in web/ai_pipeline.js mirror the Python so the
Get output directory / file path buttons stay accurate.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai_pipeline): sanitize shot_name/ai_method to prevent base-path escape
Reviewer-flagged blocker in PR #156: os.path.join's "last absolute path
wins" rule let absolute-looking shot_name or ai_method values (typos
like "/oops", or pasted full paths like "C:/Windows/junk") replace
base_directory_path entirely. The pre-#156 code had a partial defense
(.lstrip('/')) that the refactor dropped without replacing.
New _sanitize_segment helper: lstrip leading separators → splitdrive →
lstrip again. Strips leading seps BEFORE splitdrive so multi-slash
input like "///oops" doesn't get swallowed as a UNC prefix on Windows
(empirically observed — the user's intended segment name would be lost).
Applied symmetrically to directory build and filename build. Raw
shot_name kept in the return tuple for downstream label-only consumers.
Also fixes a minor JS preview mismatch at web/ai_pipeline.js: empty
base + no_subfolders=true used to render "/name.exr" while Python
returns "name.exr". Filter empties before joining the file_path.
16 new tests pin both the helper and the end-to-end escape vectors:
- TestSanitizeSegment (7 unit tests, platform-aware drive handling)
- 4 parametrised absolute shot_name escape cases
- 4 parametrised absolute ai_method escape cases
- 1 filename-leak regression
Full pytest: 102 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai_pipeline): flatten embedded separators in shot_name for filename
Maintainer-reported: with no_subfolders=true and an upstream node feeding
shot_name="ComfyUI-working-folder/LTX-Director-2K", the output was
base/ComfyUI-working-folder/LTX-Director-2K.%04d.exr — still creating
subfolders via the filename concat, defeating the toggle entirely.
Root cause: the prior fix sanitized only LEADING separators (path-escape
case). Internal separators in shot_name slipped through and became
subfolders when the filename string with embedded "/" was joined onto
output_directory by the filesystem.
Fix: filenames can't contain "/" or "\\" on any OS, so always flatten
them to "_" in the filename build. Directory build with
no_subfolders=false still uses the separators as subfolder delimiters
(intentional — users organizing into Project/v1/ai_method/vNNN want this).
With no_subfolders=true, shot_name="job/shot" now correctly produces
base/job_shot.ext (flat in base), not base/job/shot.ext.
3 new tests:
- Maintainer's exact case (no_subfolders=true + slash in shot_name)
- Belt-and-suspenders across 5 shot_name/ai_method/toggle combinations
- Verify no_subfolders=false still creates subfolders from slashes
(preserves intended use case)
Full pytest: 110 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai_pipeline): strip control chars + single-line Output Preview
Maintainer reported the file_path preview rendering as two visual lines
("n:/.../bear/...LTX-Director-2K" + newline + "_0.%04d.exr") and the
output looking like it contained a literal newline. Two contributing
causes:
1. The multi-line Output Preview widget word-wrapped long single-line
paths at the textarea edge. The data was always one logical string,
but the visual break made it look broken. Fix: set wrap="off",
whiteSpace="pre", overflowX="auto" on the input element so long
paths render on a single horizontal-scrollable line.
2. Upstream nodes (WAS Text Multiline etc.) can legitimately emit
strings with embedded \n / \r / \t — a stray paragraph break in the
widget content. Those would silently break filesystem saves and ALSO
wrap the preview at the actual newline character (not just the
textarea edge). Fix: new _strip_control_chars helper, applied at the
top of _normalize_base_path and _sanitize_segment (covers
base_directory_path, shot_name, ai_method) plus explicitly on
extension in generate_pipeline. Mirrored in JS for preview parity.
9 new tests: 6 helper unit tests, 3 end-to-end (shot_name w/ newline,
base path w/ newline, extension w/ newline).
Full pytest: 119 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai_pipeline): strict whitespace strip on extension + revert widget
Two issues, one commit:
1. Maintainer reported ComfyUI-HQ-Image-Save bailing with "Filepath
needs to end in .exr" even though our preview showed a clean
.%04d.exr suffix. Root cause: HQ-Image-Save validates via
os.path.splitext(filepath)[1].lower() != ".exr" — a single trailing
space on the extension widget (easy to acquire from a paste) makes
splitext return ".exr " with the space, which fails the strict
equality. Previous control-char strip only handled \r\n\t and
left regular spaces alone.
Fix: extension now runs through "".join(extension.split()) which
removes ALL whitespace (internal + surrounding, including unicode
whitespace and non-breaking spaces). Mirrored as cleanExtension
in JS. Also added .strip() to _sanitize_segment so shot_name /
ai_method with stray surrounding whitespace canonicalise too.
2. Reverted the Output Preview widget from the previous commit's
single-line wrap=off + horizontal-scroll layout back to the
original multi-line word-wrap (100px height, default textarea
behavior). The earlier change traded mouse-drag-selectability for
single-line display — wrong tradeoff. The path is one logical
string anyway (cleanLine strips real newlines), so visual wrap is
just cosmetic.
9 new tests:
- 6 parametrised extension whitespace cases (trailing/leading/internal
space, tab, non-breaking space) — all verify HQ-Image-Save's
splitext check passes after sanitization
- 3 parametrised shot_name surrounding-whitespace cases
Full pytest: 128 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(ai_pipeline): no_subfolders keeps version folder; v### in tooltips
Maintainer-clarified semantics for the no_subfolders toggle: it should
only drop shot_name / ai_method from the directory chain, NOT the
version folder. Previously, no_subfolders=true stripped everything,
which meant versioned outputs all collided in the same flat directory —
defeating versioning whenever the toggle was on.
New behavior matrix for no_subfolders=true:
- disable_versioning=true → output_directory = base
- disable_versioning=false → output_directory = base/v###
So truly flat output (no subfolders at all) now requires BOTH toggles,
matching VFX convention. Versioned-but-no-name-subfolders is now
expressible with just no_subfolders=true, which is the common case.
Tooltip updates:
- no_subfolders: rewritten to describe new behavior
- base_directory_path: path-shape description updated to
shot_name/ai_method/[v###]
- ai_method: clarifies subfolder application is gated by no_subfolders
- version, disable_versioning: vNNN → v### (Nuke-style convention
consistent with the existing .%04d.exr default)
2 new tests:
- no_subfolders=True + disable_versioning=False → base/v###
- no_subfolders=True + disable_versioning=True → base (truly flat)
Existing tests use the disable_versioning=True default with
no_subfolders=True so they continue to assert base-only — they still
pass under the new logic since version_str="" gets filtered out of the
join.
Full pytest: 130 passed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(ai_pipeline): strip sentinel path components
* fix(ai_pipeline): resolve EasyUse get-set preview values
* docs: document ComfyUI preview button debugging
* fix(ai_pipeline): align drive segment sanitization
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
bc7ec09971 |
Fix sidebar workflow persistence recovery (#155)
* fix(sidebar): recover workflow saves after fallback * fix(sidebar): merge workflow fallback recovery * fix(sidebar): clear redundant workflow fallback |
||
|
|
ff79b0f418 |
feat(video): add Easy_VideoCombine (#154)
* feat(video): add Easy_VideoCombine with absolute-path support Adds a Koolook subclass of VHS VideoCombine that bypasses ComfyUI's output-directory sandbox when filename_prefix is an absolute path — the VFX-pipeline missing piece for writing renders into a project tree (e.g. E:/projects/shot01/v003) instead of ComfyUI/output/. Implementation: - k_video_combine.py: ~60 LoC subclass + a pure _resolve_abs_target helper. Scoped-patches folder_paths.get_save_image_path for the duration of one combine_video() call so upstream's encoder, audio mux, batch manager, and progress bar run unchanged. No VHS code copied. Self-skips registration when VHS isn't importable (clean one-line stderr notice). - Discriminator: os.path.isabs(filename_prefix). No new input pin; pattern borrowed from spacepxl/ComfyUI-HQ-Image-Save's SaveEXR. - Optional create_path_if_missing BOOLEAN (default False) so typos surface as FileNotFoundError instead of spawning stray dirs. License audit (2026-05-16): - VHS upstream: GPL-3.0 (verified via raw LICENSE fetch). - HQ-Image-Save pattern source: MIT, copyright 2023 spacepxl. - Koolook: already GPL-3.0; both upstreams are compatible. - No source code from either upstream is incorporated. Verification: 18 pytest cases green (12 existing + 6 new covering relative passthrough, absolute resolution, create-on/off branches, trailing-separator normalization, and empty-basename rejection). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(starter-preset): pre-favourite Easy_VideoCombine Adds the new node to the shipped starter_preset.json picks so fresh installs surface it in the Kforge Labs sidebar's Nodes section without the user having to discover it via Comfy's stock search first. Pairs naturally with the existing LoadEXR / SaveEXRFrames picks from HQ-Image-Save. Existing installs with non-empty picks are unaffected (the seeder skips them by design); they can either Load -> "starter" in the Snapshot dialog or hit "+" once in the Nodes section to pull it in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(dev-sync): include k_video_combine.py in RUNTIME_PATHS Without this entry the new Easy_VideoCombine module is never copied into the dev ComfyUI install, so the node fails to register on restart with a silent ImportError. Caught when the maintainer restarted ComfyUI and couldn't find the node in the search palette. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(starter-preset): include all Koolook nodes in default picks Fresh installs should land on a populated Nodes (favorites) panel that immediately surfaces what Koolook provides — otherwise new users see only third-party picks and have no entry point into the Koolook-native workflow. Adds the 8 currently-registered Koolook node IDs to the shipped starter preset alongside the existing third-party essentials: EasyAIPipeline, EasyResize_Koolook, EasyWan22Prompt, Easy_Pattern, Easy_VideoCombine, Easy_hdr_VAE_decode, Easy_hdr_VAE_encode, KoolookLoadCameraPosesAbsolute, easy_ImageBatch Also adds Easy_VideoCombine to the README's Included Nodes table. Existing installs with non-empty picks are unaffected by the seeder (by design). To pull the Koolook defaults into an existing install, Quick Save first, then Snapshot -> Load -> "starter". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(video): reach VHS_VideoCombine via nodes.NODE_CLASS_MAPPINGS The previous `from videohelpersuite.nodes import VideoCombine` fails inside ComfyUI's loader context because VHS uses relative imports throughout (`from .logger ...`, `from .utils ...`) that only resolve when VHS is loaded through ComfyUI's importlib spec — not through a plain absolute import path. The error surfaced as No module named 'utils.install_util'; 'utils' is not a package at Koolook load time, which caused the node to self-skip with an empty NODE_CLASS_MAPPINGS — invisible in ComfyUI's search palette. Fix: look up VHS_VideoCombine through `nodes.NODE_CLASS_MAPPINGS`, which is the documented cross-pack interface. ComfyUI's loader (custom_nodes/.../__init__.py exec_module → merge into global) runs to completion for each pack before moving to the next, and VHS sorts before Koolook alphabetically in every layout we've seen, so by the time Koolook's __init__.py runs the global registry already has VHS_VideoCombine in it. Verified against ComfyUI's nodes.py load_custom_node() at load_custom_node() lines 2196 (exec_module) and 2225-2228 (merge), which guarantee this ordering. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(video): add output_directory input for split path/name editing Overloading filename_prefix for absolute paths means losing the "just type a name" affordance — once you put the path there, there's nowhere to specify just the file root. Across many renders into the same project tree this becomes tedious: every iteration retypes the whole path. Adds an optional `output_directory` STRING input. When set: - filename_prefix is treated as just the filename root; any path components are stripped (so accidental typos like "sub/clip" don't double-up under the directory). - Absolute output_directory writes directly to that location. - Relative output_directory joins under ComfyUI's output/. When output_directory is empty, the original overloaded filename_prefix behavior is preserved unchanged (back-compat for the one-field flow). Implementation: - New pure helper `_compose_prefix(filename_prefix, output_directory)` combines the two fields into one effective prefix. The existing isabs discrimination then handles sandboxed vs absolute writes uniformly downstream. - 6 new tests cover all combinations: empty/abs/relative directory, path stripping from filename_prefix, trailing-separator trim, empty-name fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(video): coerce frontend 'undefined' STRING values to empty The ComfyUI frontend serializes a STRING widget with empty default as the literal string "undefined" when left untouched (a known long-running quirk). Without defensive normalization, an untouched output_directory was treated as a real directory name and prepended to the user's path: filename_prefix = "E:/renders/clip_v003" (typed by user) output_directory = "undefined" (frontend default, not typed) _compose_prefix(...) -> "undefined/clip_v003" (relative) _resolve_abs_target -> None (not absolute) super().combine_video -> writes to <output>/undefined/clip_v003_* The QuickTime ended up at <ComfyUI>/output/undefined/<name>.mov with a spurious `undefined/` subfolder, instead of at the absolute path the user actually specified. Fix: a `_normalize_text_input()` helper coerces the literal strings "undefined" / "null" / "None" (case-insensitive, post-strip) to "". Applied to both filename_prefix and output_directory at the top of combine_video() before any path composition. 4 new tests cover the sentinels and a regression test for the exact path-composition behavior that produced the bug. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(video-fe): wire format-specific widgets for Easy_VideoCombine VHS's frontend extension (web/js/VHS.core.js:2030) gates its format-widget machinery on `nodeData?.name == "VHS_VideoCombine"` — strict-equality on the registered node ID. Our subclass uses a different ID (`Easy_VideoCombine`), so none of VHS's per-format widget rendering ran. The user saw: [VideoHelperSuite] - WARNING - Missing input for p has been set to hq on every execution (profile defaulting to "hq" for ProRes), and had no way to pick anything other than the default — because the profile dropdown was never rendered. Fix: a minimal frontend extension at web/easy_video_combine.js (~80 LoC) that replicates `addFormatWidgets` for our node name. Hooks the format dropdown's callback to add/remove per-format widgets the same way upstream does. Self-contained — does not import from VHS's module (their helpers aren't exported as ES imports). Verifies via the actual ComfyUI install after dev-sync; static preview servers can't exercise it (depends on LiteGraph's registered_node_types at runtime). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(video): strip 'undefined' from anywhere + one-shot debug log The previous normalization only caught the EXACT literal "undefined" / "null" / "None" at field-level. Adds two stronger defenses for the case where a sentinel still slips through: 1. _normalize_text_input now also strips surrounding quotes (paste- from-Explorer artifact) and handles embedded newlines by taking the first non-empty non-sentinel line (defends against multi-line Text Multiline inputs with trailing blank or junk lines). 2. New _strip_sentinel_components helper drops any path component that is exactly a sentinel string from the composed effective_ prefix — belt-and-suspenders for any path that somehow carries `\undefined\` as a literal segment by the time it reaches us. Also adds a one-shot print() of fp_raw, od_raw, normalized values, and effective_prefix at the top of combine_video. The user is seeing an undefined/ folder under their custom path despite the field-level normalization, and pure code tracing doesn't reproduce it — the log will surface what kwargs actually contains at run time so we can fix the real cause if defensive stripping doesn't already handle it. To be removed in a follow-up once root cause is known. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(video): trim node docstring + tooltips, remove debug print - Class docstring collapsed from ~20 lines to a single sentence (the frontend was showing it as the node description on right-click, cluttering the UI). - Widget tooltips trimmed to one short sentence each — same intent, no walls of text. - Debug print() added in |
||
|
|
55dab39c9b | Trim workflow context menu move list (#153) | ||
|
|
f74b30603e | Release v0.3.2 (#149) v0.3.2 | ||
|
|
119a1e3e6c | docs: refresh release guide screenshots (#148) | ||
|
|
c7961998aa |
feat(snapshot): refine recovery choices and snapshot dialog styling (#146)
* Refine snapshot load dialog recovery UI * Make snapshot load recovery choices explicit * Align snapshot picker styling * Respect Comfy theme variables in snapshot dialogs * Document superseded snapshot recovery design * Fix load recovery selection races * Prevent duplicate snapshot dialog actions * Harden snapshot recovery UI fallbacks |
||
|
|
8e489ad37a |
feat(snapshot): redesigned Save/Load dialogs + folder picker (#137) (#144)
* feat(snapshot): row-augment max(periodic, pre_load_*) + pytest infra (#137) Server-side preparation for the snapshot dialogs redesign. The Load dialog's "newer auto-save available" row flag now reflects the freshest recovery file regardless of which mechanism wrote it — before this we only stat'd ``periodic.json``, missing cases where the user's most recent Load wrote a fresher ``pre_load_*.json`` snapshot inside the same autosave subfolder. Extracts ``_latest_autosave_mtime(autosave_dir, named_mtime)`` from the inline block in ``list_presets`` so the scan is unit-testable. The helper walks ``periodic.json`` and every ``pre_load_*.json`` once, returns the max mtime if strictly newer than the named file, else ``None``. Same shape as before, broader source set. Introduces pytest as a first-class test runner: * ``tests/server/test_listing_autosave_mtime.py`` — 5 row-augment test cases (periodic-only, pre_load-only, max-of-both, no-newer, missing-dir). * ``[tool.pytest.ini_options]`` in ``pyproject.toml`` scopes discovery to ``tests/`` and uses ``importmode=importlib``. * ``[project.optional-dependencies] test = [...]`` defines the test extra (pytest + aiohttp, which is a runtime dep of ``koolook_routes`` that must be importable even when only a pure helper is under test). * ``.github/workflows/ci.yml`` gains a ``pytest`` job that installs via ``pip install .[test]`` and runs ``pytest -q``. Guards ``__init__.py``'s relative imports in a ``try / except ImportError / else`` block. Pytest's package collector walks up from test files looking for ``__init__.py`` and tries to import the one at the repo root — but the relative imports inside it only resolve under ComfyUI's custom-node loader, not under a standalone Python import. Without the guard, every pytest run aborts during collection with ``attempted relative import with no known parent package``. The fallback emits a one-line diagnostic and registers empty mappings so the package directory can still be imported by tooling. ComfyUI's loader still hits the ``else`` branch and behaviour is unchanged for real users. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(snapshot): folder picker + agent-driven visual harness (#137) Commit 2 of the snapshot dialogs redesign. Introduces the navigate- into folder picker from mockup section 6 — the picker that opens when the user clicks Save to… / Load from… in the redesigned dialogs (and that the Settings cog will be removed in favour of, once commit 6 lands). Path input is the source of truth: type/paste + Enter navigates, clicking a folder row drills in, ↑ Up climbs one level. Files appear greyed below the directories as a "yes, this is the folder I expected" affordance. End-visible path overflow: the path input uses ``direction: rtl`` + ``unicode-bidi: plaintext`` so when the path exceeds the input width the leading ``/Users/…`` clips on the left, never the trailing folder name. Per mockup section 6. Visual harness — the path the issue called out as "visual verification is a hard gate": * ``.claude/launch.json`` defines ``design-server`` (``python3 -m http.server 8765``). Agent / maintainer can spin this up via ``preview_start`` or directly from a terminal. * ``docs/designs/_harness/folder-picker.html`` mounts the real ``showFolderPicker`` from ``web/sidebar/modals.js`` against a canned filesystem, with one trigger button per mockup-illustrated state (default / new-folder-input / empty / error). Cache-busts module imports so editing modals.js while the harness is open picks up fresh code on reload. * ``docs/maintainers/visual-harness.md`` documents the workflow, when to use the harness vs. dev-sync, and the backtick-in-CSS- comments pitfall that broke this commit twice during development. * ``CLAUDE.md`` now lists the harness as the preferred agent-driven path; dev-sync stays the integration-level gate. Server: ``/koolook/presets/browse`` gains an opt-in ``?files=1`` parameter that returns child JSON files alongside the directories. Default response shape unchanged so the legacy Settings dialog keeps working. ``_list_child_files`` mirrors ``_list_child_dirs``'s swallow-OSError-per-entry policy so a broken symlink mid-listing doesn't sink the whole response. Tests: six new pytest cases in ``tests/server/test_browse_list_children.py`` covering directory sorting, autosave-subfolder hiding, JSON-only filter, case-insensitive extension match, and the empty-result paths. ``pytest`` runs all 11 tests cleanly under the existing CI job from commit 1. What's still verified via dev-sync (not via the harness): * Real Comfy theme tokens (the harness fakes ``--comfy-menu-bg`` etc. with dark-theme defaults). * End-to-end Save → Load round-trip across the new library path. * Picker interaction with ComfyUI's keyboard manager. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(snapshot): modal headers stay title-only; route gloss to tooltip (#137) Follow-up to the folder picker commit. Maintainer flagged that the "Where snapshots are saved and loaded from." subtitle below the title was extra information the user doesn't need at first glance — if the title isn't self-explanatory, fix the title, don't pad it with a sentence. The mockup at docs/designs/snapshot-dialogs.html section 6 shows a title-only header for exactly this reason. Locks the rule in as a project convention so every dialog box behaves the same way going forward: * docs/maintainers/conventions.md — new "Modal headers" section spells out the rule, what counts as a "descriptive subtitle" (remove) vs. a "functional info row" (keep), and how to plumb explanatory copy via a hover tooltip on the title instead. * web/sidebar/modals.js — makeModalShell now accepts ``titleTooltip``, applied as the ``title`` attribute on the modal title element so the gloss is mouseover-discoverable but never visible by default. showFolderPicker drops its ``subtitle`` param (it was added one commit ago, no other callers) in favour of ``titleTooltip``. * docs/designs/_harness/folder-picker.html — harness re-points its explanatory text to ``titleTooltip``. Verified in the harness: the visible header is title-only, the tooltip surfaces on hover. Applies prospectively to the Save / Load dialogs (commits 3 and 4) — they will also be title-only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(snapshot): redesigned Save dialog with inline library row + Save to... + 4-state button (#137) Commit 3 of the snapshot dialogs redesign. The Save dialog is now a single modal (was three: three-way overwrite / input prompt / confirm) that matches mockup section 2: * Inline library-path info row at the top of the body — "Saved to" label on the left, "Open folder ↗" link on the right, full path below. Pure information; every action lives in the bottom command bar. * Bottom command bar with split layout: Save to... on the left, Cancel + Save as new... + Save on the right (spacer in between). * Save to... opens the folder picker from commit 2. On Use this folder, the dialog calls saveSettings() to persist the new library path, refreshes its in-place library row, and flips the primary button into Dirty state. * The primary Save button cycles through the four states from mockup section 5: - Default ("Save") — clean state, primary blue. - Dirty ("Save to new folder") — folder differs from the one the dialog opened with; the label spells out the consequence. - In progress ("Saving...") — disabled, dimmed primary. - Done ("Saved") — disabled, subtle green, brief pause before the dialog closes. * Save as new... still routes through the existing showInputModal plus presetExists guard, but the prompt no longer carries a library-path subtitle — the parent dialog already shows the target. Convention from the previous commit applied. tree.js now passes the new capability functions (saveSettings, browseDirectories, createBrowseDirectory, revealPresetFolder) into showSaveSnapshotDialog so the dialog can invoke the folder picker end-to-end inside the Save flow. Visual harness: docs/designs/_harness/save-dialog.html exercises all five mockup states (default tracked, untracked Save-as-new, dirty, in-progress, done) so the maintainer (or future agent) can re-verify without dev-syncing. Verified all states render correctly in the preview server. Static guard against the recurring backtick-in-CSS-comments trap: tests/sidebar/test_css_template_literal.py asserts that the CSS template literal in web/sidebar/constants.js contains exactly two backticks (open + close) and nothing in between. The trap bit this work three times (commits 2 and 3) — a literal backtick inside a comment closes the template early and silently bricks the entire sidebar. The test runs under the same pytest job introduced in commit 1. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(snapshot): redesigned Load dialog with scoped recovery + inline delete (#137) Commit 4 of the snapshot dialogs redesign. The Load dialog is now a single modal matching mockup section 3: * Inline library row at the top, same shape as the Save dialog — "Loaded from" label + Open folder ↗ link + leaf folder name + full path. No 📂 emoji button (the link supersedes it). * Preset rows: click to load. The "Replace current state?" confirm modal is gone — clicking Load is the intent, the dialog title already says Load snapshot, no double-confirm. * Scoped recovery: when a preset has a newer autosave (per the server-side row-augment from commit 1), the first click DOES NOT load. It expands exactly ONE recovery row directly beneath the parent, showing the freshest entry (newest of periodic.json or pre_load_*.json) with its kind badge + meta line. The user then chooses: re-click the named row to load the named version, OR click the recovery row to restore the auto-save. Both options are surfaced inline; the user picks by clicking the one they want. * Inline delete confirm: × outlines the target row red, the bottom Close button transforms to 'Yes — delete "<name>"' (danger styling). A second click commits; Escape cancels and reverts. No second modal. * Bottom command bar matches mockup section 3 split layout: [Load from…] on the left, [Close] on the right. Load from… opens the folder picker from commit 2; on Use this folder it persists via saveSettings() and refreshes the listing. The pre-#137 YES/NO choice modal (``promptApplyChoice``) and the collapsible Recovery auto-saves <details> section are both removed — the scoped-recovery inline row + the click flow above subsume their job. (Commit 5 in the issue plan was specifically for dropping the YES/NO modal, which happens here as a natural consequence of the routing change; commit 5 will be a small follow-up if needed.) Behavior preserved from the pre-#137 implementation: * doNamedLoad — pre-load auto-save backup chain, partial-failure tracker handling, identical toast messages. * doAutosaveRestore — same backup chain, same markStateAutosaved() semantics, same restored-name derivation from subdir. Tree.js plumbs the three new caps (saveSettings, browseDirectories, createBrowseDirectory) into showLoadSnapshotDialog so the dialog can invoke the folder picker end-to-end. Visual harness: docs/designs/_harness/load-dialog.html exercises default, empty, scoped-recovery, and pending-delete states. Verified all four render correctly in the preview server. Net diff: -56 lines in the sidebar source (the redesign deletes more than it adds because the YES/NO modal + collapsible recovery disclosure + confirm modal for plain loads are all gone). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(designs): reconcile snapshot redesign sketch with shipped behaviour (#137) Commit 5 of the snapshot dialogs redesign. The code-level removal of the autosave-newer YES/NO modal happened in commit 4 as a natural consequence of the scoped-recovery inline row taking over its job; this commit reconciles the design docs so they describe what was actually built. docs/designs/snapshot-dialogs-redesign.md: * "What changes" item 4 reworded — the YES/NO modal is dropped; the affordance is now an inline scoped recovery row that opens directly beneath the clicked preset. * "What changes" gains items 2, 3, 5 covering the Save/Load dialog redesigns and the inline delete confirm that landed in commits 3 and 4. * Mermaid flow rewritten to reflect the new routing (preset click → scoped recovery expand → user picks named or recovery row), matching commit 4's onPresetClick implementation. docs/designs/snapshot-dialogs.html "Locked-in decisions": * Updated the autosave-modal item from "modal head reads X" to "modal — dropped" and explains the inline scoped recovery row replacement. * Added the title-only modal header convention (commit 2 follow-up) pointing to docs/maintainers/conventions.md. * Added a pointer to the visual harness workflow added in commit 2. No code changes — the YES/NO modal was already gone after commit 4. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(snapshot): remove Settings cog + showSnapshotSettingsDialog + CHANGELOG (#137) Final commit of the snapshot dialogs redesign. The Settings cog disappears from the Snapshot toolbar — the snapshot row is now status indicator | Load | Quick Save | Save, matching mockup section 1. Library-path management now lives inside the Save dialog ("Save to…") and Load dialog ("Load from…"), both of which open the folder picker introduced in commit 2 and persist via the existing `saveSettings` route. Removals: * `showSnapshotSettingsDialog` (~325 lines) — the dialog body, its inline directory browser, and the "save folder location" state machine. The same browse functionality lives in `showFolderPicker` now; the same persistence happens via `saveSettings()` called from inside Save / Load. * `showThreeWayDialog` (~45 lines) — only consumer was the pre-#137 Save flow (overwrite / Save as new / Cancel), which the redesigned Save dialog absorbed into its primary action bar. * `renderLibraryLocation` (~17 lines) — replaced by inline rendering in the Save and Load dialogs' library rows. * Settings cog toolbar button + `openSettingsDialog` wiring in tree.js (~12 lines). * Orphan `.koolook-browse-*`, `.koolook-path-input-row`, `.koolook-load-library-row*`, `.koolook-settings-save-note` CSS rules in constants.js (~16 lines) — only consumed by the removed dialog. `.koolook-settings-folder-name` and `.koolook-settings- folder-path` stay; the new Save / Load library rows reuse them verbatim (git-blame continuity for the visual style). Adds: * CHANGELOG.md [Unreleased] entry summarising the whole #137 feature — picker, redesigned Save/Load dialogs, scoped recovery, inline delete, removed Settings cog, pytest infrastructure. Net diff for this commit: -377 lines. Cumulative PR diff (commits 1–6 + harness/convention follow-ups): all six issue ACs satisfied. The maintainer should dev-sync once before merging to confirm real-Comfy theme tokens render correctly (harness fakes the dark-theme defaults) and end-to-end Save → Load round-trips against the live preset folder behave. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(ci): install pytest + aiohttp directly, not via pip install . (#137) The Pytest job introduced in commit 1 of this PR was failing on CI because ``pip install .[test]`` tries to build a wheel of the entire project. setuptools's flat-layout discovery then trips on the non-Python ``web/`` and ``forks/`` directories at the repo root and errors out with "Multiple top-level packages discovered in a flat- layout: ['web', 'forks']". The project is a ComfyUI custom node loaded directly from its source directory, not a pip-installable package. There's no wheel to build. Install the test deps explicitly instead — the ``[project.optional- dependencies] test`` block in pyproject.toml is the spec, CI mirrors it by hand. Worked locally because the venv install path went straight to ``pip install pytest aiohttp`` and never touched the project. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(snapshot): untracked dirty state + Load dialog Escape listener leak (#137) Two findings from the external review on PR #144: **Blocker — Save dialog's primary action with no tracked preset + folder changed via Save to...** ``updatePrimaryLabel`` was entering the "dirty" state whenever the library path diverged from the original, regardless of whether a tracked preset existed. The primary click handler then routed "dirty" to ``doOverwrite(current)`` — and with ``current === null``, ``sanitizeName(null)`` would throw / silently coerce to an empty string instead of asking the user for a name. Fix: ``isDirty()`` only matters when there is something to overwrite. With no tracked preset, the primary stays in ``default-new`` regardless of folder divergence. The Save to... button still updates the library row, so the user has feedback the destination changed; the eventual primary click then routes to the Save-as-new name prompt and writes into the new folder cleanly. Verified in the visual harness: opening Save as untracked, switching the library to ``/Volumes/Shared/different-library`` via the picker, then inspecting the primary button: * Label stays "Save" (was "Save to new folder" pre-fix) * Tooltip "Choose a name and write the snapshot." (was "Write into /Volumes/Shared/different-library.") * Library row still reflects the new path (saveSettings DID run) **Major — Load dialog leaked a document keydown listener per session** The inline-delete Escape-to-cancel handler was registered with ``document.addEventListener`` but never removed. Each open/close cycle of the Load dialog accumulated another stale handler; long sessions ended up firing the cancelDelete check on every Escape press across every dialog (no-op because ``pendingDelete`` was null in closed dialogs, but the listener was still alive and called). Fix: capture the handler by name, attach on open, tear down on close by wrapping ``overlay.remove`` (mirrors the pattern the modal shell already uses at ``modals.js:52-66`` for its own Escape handler). Verified by instrumenting ``document.{add,remove}EventListener`` and opening + closing the Load dialog five times in a row: every keydown listener added during the cycle was removed before the next cycle opened. No accumulation. CI gates: ruff, compileall, bandit (via preflight equivalent), pytest 12/12, preflight 4/4 — all green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
41ff4de460 |
feat(workflows): add right-click update from selection or canvas (#133)
* feat(workflows): add context-menu update from current canvas * feat(workflows): update from selection or canvas * refactor(workflows): keep sidebar app access inside canvas_io * fix(workflows): repair selection-update block syntax and duplication * nit(workflows): clarify selection-unavailable toast path --------- Co-authored-by: kflame99 <261674183+kflame99@users.noreply.github.com> |
||
|
|
e87d111521 |
docs(designs): snapshot dialogs redesign mockup + Mermaid flow (WIP) (#136)
* docs(designs): snapshot dialogs redesign mockup + Mermaid flow (WIP) Design exploration for consolidating the Snapshot Settings cog into the Save and Load dialogs, plus the related autosave-newer fix. Discussion artifacts only — no code changes. - docs/designs/snapshot-dialogs.html: side-by-side mockups across six sections (toolbar, Save dialog, Load dialog with default / Recovery expanded / inline delete-confirm states, autosave-newer modal, button-state pattern, folder-browse picker) - docs/designs/snapshot-dialogs-redesign.md: scope sketch + Mermaid flow - docs/designs/README.md: register the two new design files in the index Single-library design stays (documented intent per docs/maintainers/curated-sidebar.md). Redesign only consolidates surface; implementation lands in a separate PR after layout details are locked in. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(designs): refine autosave-newer modal + single-row recovery groups Two scoped tweaks to the snapshot dialogs mockup: - Section 4 (autosave-newer): drop the "Replace current state?" question since the user already chose Load; promote "Auto-save is newer than the saved version" to the only head message. Render the two options as standard preset-rows (filename + picks/workflows/date meta) so the layout mirrors the Load dialog list. Buttons collapse to NO / YES — load latest on one row. - Section 3 (recovery expanded): show only the newest row per <preset>_autosave/ group — whichever of Pre-load or Periodic is most recent — instead of listing both kinds. Annotation also notes the Recovery section auto-expands when a preset row is clicked. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(designs): lock recovery behavior — hidden by default, one row per clicked preset When the user clicks a preset row (Koolook_v03, starter, etc.) the recovery section opens and reveals only that preset's <preset>_autosave/ folder, with a single row inside — whichever of Pre-load or Periodic is newest (the system picks). _unsaved_autosave/ no longer renders inside this preset-scoped view since it isn't tied to the clicked preset. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(designs): polish section 6 + recovery row layout Folder-browse picker (section 6): - Switch to standard navigate-into model — clicking a folder drills in, body lists that folder's contents (subfolders clickable, files greyed- out for context). Path input shows where you are; "Use this folder" commits the current path. Drops the sibling-list + selected-tag model. - Single-line toolbar: ↑ Up next to the path input; secondary parent- path strip removed. - Path input keeps the END visible when overflowing (direction:rtl + text-align:left), so a long path shows the meaningful trailing folder instead of /Users/… — and example path is now long enough to demonstrate it. - Header restyled to match other modal heads (15px title, dark bg, bigger padding); descriptive subtitle removed. - Bottom bar: New folder… stays left; Cancel + Use this folder push right (.picker-foot .spacer rule, since the existing flex-spacer was scoped to .modal-actions). - Redundant "SELECTED FOLDER" stripe removed — selected-row blue accent is sufficient confirmation; folder list gains the freed vertical room. - Example folder rename _unsaved_autosave -> default_autosave (label only — on-disk constant unchanged; rename is an open suggestion to drop the self-contradicting "unsaved autosave" term). Recovery row (section 3): - Layout: badge on top, single combined meta line below in order "<picks> · <workflows> · <date>" — matches preset-row shape so both rows read the same way. - Padding bumped to 10px 12px so recovery-row and preset-row are the same box size; badge nudged 1px up + meta nudged 1px down for breathing room. - Recovery section now scopes to the clicked preset only: orphan _unsaved_autosave/ group removed from the "Koolook_v03 clicked" view, since when a preset row is clicked the dropdown should only show that preset's auto-saves. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: require visual verification before requesting design-driven review When implementing a UI change that has a corresponding mockup in docs/designs/ (e.g. snapshot-dialogs.html, sidebar-icon-proposals.html), the design file is the spec. Before the work is brought to the maintainer for review, the implementing agent must: 1. Render the implementation in a real browser — dev-sync for runtime sidebar work, python3 -m http.server (or the Launch preview panel) for standalone HTML mockups. 2. Screenshot the implemented UI at every state the mockup shows. 3. Diff each screenshot against the mockup. Match it or document the deviation with a rationale. 4. Only then request review. The maintainer should never be the first set of eyes on rendered output. Type checks and tests verify code correctness, not visual fidelity to the design — if a mockup exists for the change, visual verification is a hard gate, not optional polish. Rule lives in three places, each hit from a different entry point: - CLAUDE.md (loaded at every session start; canonical) - docs/designs/README.md (discovered when opening the designs folder) - docs/maintainers/dev-iteration-loop.md (step 4 in the iteration loop) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * skills(tdd): add project-specific TDD skill modeled on ForgeFlow's When implementation work on docs/designs/ mockups begins, /tdd on this repo should pick up a project-level skill that knows ComfyUI-Koolook's actual machinery — not just generic TDD principles. Two files: - SKILL.md — keeps the global TDD philosophy (vertical slices, behaviour-not-implementation, refactor only after GREEN) and adds a project-specific tail covering: • code surface: where Python nodes vs sidebar JS vs design specs live • test surface: what CI runs today (ruff, compileall, bandit, preflight_release.py) and explicit guidance on introducing pytest / vitest "only when there's a behaviour that genuinely needs unit testing" — same PR as the first test, never speculative • the dev-sync iteration loop for runtime sidebar work • high-value test targets (node registration, route validation, VAE dispatch, snapshot row augmentation) • drift patterns specific to this codebase (vendored upstream into MAIN, renamed namespaced node IDs in saved workflows, hardcoded user paths, dev-sync triggered from an automated flow, …) • final scope-alignment check routing (clean → proceed; drift → show report, never revert) - visual-verification.md — the secondary procedure for cycles whose assertion is CSS / layout / structural. Procedure split for the two shapes of work in this repo: • runtime sidebar (web/sidebar/*.js): dev-sync → restart ComfyUI → screenshot → diff against docs/designs/<feature>.html • standalone HTML mockups (docs/designs/*.html): Launch preview panel or python3 -m http.server (no new package installs per supply-chain rules) Plus a structured definition of what "diff against the mockup" means (spatial, hierarchy, states, chrome, text — not pixel-equality), so it isn't a vague gate. Generic companions (deep-modules, mocking, refactoring, interface- design, scope-check, tests) intentionally not copied — agents follow the global ~/.claude/skills/tdd/ pointers in SKILL.md for those. Avoids sync-drift on principle docs and keeps this skill focused on what's genuinely project-specific. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9dab85c695 |
feat(easy_pattern): add test-pattern node + auto-restart dev-sync (#135)
* feat(easy_pattern): add test-pattern node + auto-restart dev-sync Adds an `Easy_Pattern` node (Koolook/Testing) for solid-color frame batches with optional per-frame index overlays — verifies sequence merging, frame insertion, batch indexing, and video-pipeline frame order at a glance. Color inputs use the same `White/Black/Gray/Custom` dropdown idiom `EasyResize_Koolook` already exposes for pad/panel colors, so "Gray" means the same 50% sRGB across nodes. `scripts/sync_to_dev.py` now restarts the live ComfyUI server after a successful sync (via ComfyUI-Manager's `/manager/reboot`). Default-on because dev-sync is only ever invoked to see new code, and Python custom-node files load once at server start — `--no-restart` is the escape hatch. The `RUNTIME_PATHS` manifest also picks up `k_easy_pattern.py` so adding a new top-level node doesn't silently break the pack at import time on the next sync. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sync-dev): restrict restart URL to http(s) for Bandit B310 `urllib.request.urlopen` accepts arbitrary schemes including `file://`, which Bandit B310 flags as a path-traversal vector. The script's typical caller is the maintainer's own shell, but a typo in `--restart-url` could surprise — and the static gate is right to flag it. Validate the scheme is http/https before the urlopen call and annotate it so Bandit accepts the now-truthful narrowing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
bf02db8ad8 |
feat(easy_image_batch): source_batch + source_frames list + selected_… (#134)
* feat(easy_image_batch): source_batch + source_frames list + selected_* outputs Three-mode input system, two new "selected only" outputs, and an alpha output convention that's explicit about its role. Inputs - source_batch (IMAGE, optional): pre-batched stack, e.g. straight from Load Video. - source_frames (multiline STRING): VFX frame numbers separated by commas, newlines, spaces or any mix. Picks from source_batch and places at the same timeline position. Removes the 4-keyframe ceiling. - placeholder_color: now offers Black / Gray / White (was Black / Gray). - invert_alpha (BOOLEAN, default off = "inpaint"): flips alpha to compositing convention (selected=white) when on. - image1 is now optional (you can drive the node entirely from source_batch + source_frames). At least one of image1 or source_batch must be connected. - start_frame default changed from 0 -> 1 to match VFX 1-based numbering. Mode rules - Mode 1 -- per-slot images: connect imageN, set imageN_frame. - Mode 2 -- source_batch + 4 slots: when imageN is unconnected, imageN_frame doubles as the source pick index AND timeline target. - Mode 3 -- source_batch + source_frames list: when the list is non-empty, the 4 slots contribute ONLY where imageN is explicitly connected -- the imageN_frame defaults (0, 4, 8, 12) do NOT pick from source_batch in this mode, so the list fully controls the selection unless an imageN override is wired. - Priority: source_frames list runs first; manual slots run after and override at the same target index. Output dedup is automatic (set-based). Outputs - image_batch (IMAGE): full timeline with placeholders. Unchanged. - alpha_batch (MASK): renamed from mask_batch; same default convention (selected=0.0/black, empty=1.0/white) so existing wires work unchanged. Toggle invert_alpha for compositing-style. - selected_image_batch (IMAGE, NEW): only the keyframes that landed, packed back-to-back in timeline order. Empty (length 0) if none placed. - selected_frames (STRING, NEW): comma-separated VFX frame numbers, sorted ascending and deduped. Same format as the source_frames input -- round-trips into another easy_ImageBatch instance. Docs - Updated class docstring with input modes + worked example (surfaces in ComfyUI's node tooltip). - New per-node user guide: docs/user_guide/nodes/koolook_image/easy_image_batch.md. - docs/user_guide/README.md graduates easy_ImageBatch from "TBD" to a real link. - CHANGELOG: Unreleased Added/Changed entries cover the full change set. Backward compatibility - Existing workflows wired to image_batch + mask_batch keep working; the second output slot position is unchanged. - The 4-keyframe screenshot pattern (source_batch + 4 slots, no list) is unchanged in behaviour. - start_frame default change affects only newly added nodes; saved workflows keep the value they were saved with. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(easy_image_batch): cut window — rename start_frame -> cut_start_frame, fix source-pick math The 2nd output of Easy Image Batch (Koolook) gets a built-in cut window, replacing the workflow pattern of feeding the output through an external ImageFromBatch (batch_index, length) slicer. Conceptual rename - `start_frame` -> `cut_start_frame` everywhere. - The output now represents VFX frames [cut_start_frame .. cut_start_frame + total_frames - 1]. - Frames placed outside the cut window are dropped from the cut silently and listed in a single end-of-run summary log (replaces the previous per-frame "out of range" warnings). Math fix (real bug) The previous code conflated source pick index and output index under a single `target_index = vfx - start_frame`. That happened to work when start_frame=1 (source pick index = vfx - 1, by coincidence), but produced a wrong source pick whenever start_frame != 1. Now: source pick index = vfx_frame - 1 # hardcoded VFX 1-based output (cut) index = vfx_frame - cut_start_frame # cut window The two are validated independently: - source-side range error -> per-frame warning ("VFX frame N doesn't exist in source_batch") - output-side range -> silent drop, collected into the end-of-run summary Defaults - cut_start_frame=1 (was start_frame=1, so default behaviour for source_batch starting at VFX frame 1 is identical). - total_frames default 16 -> 81 (matches typical VFX shot length). - imageN_frame defaults 0/4/8/12 -> 1/5/9/13 (valid 1-based). - imageN_frame min 0 -> 1 (frame 0 is invalid under VFX 1-based). Console summary Always logged so it's easy to confirm what the node produced. Shape: [easy_ImageBatch] cut window: frames 41..121 (81 frames). 6 placed; 2 outside cut: 1, 27. Docs - Class docstring rewritten with the cut-window model + Examples A (cut_start_frame=1, no cut) and B (cut_start_frame=41, cut). - docs/user_guide/nodes/koolook_image/easy_image_batch.md fully updated to match. - CHANGELOG: new "Changed" entry under [Unreleased] explaining the rename, math fix, defaults bump, and migration notes. Migration notes for users of the previous PR commit - Saved workflows that had start_frame=1 (the default) keep producing identical output -- the math is unchanged in that case. - Saved workflows that set start_frame to a non-1 value will see different source picks (the old code was actually picking the wrong source frame, the new code picks correctly). Re-check those. Co-authored-by: Cursor <cursoragent@cursor.com> * feat(easy_image_batch): shift imageN_frame defaults to 5/9/13/17 The previous 1/5/9/13 cluster put all four default keyframes inside the first 13 frames of an 81-frame timeline. Shifting to 5/9/13/17 keeps the same stride-of-4 but leaves a small gap at the start, which reads cleaner in the node UI and gives a more representative spread of where keyframes commonly land. Updates the doc table and CHANGELOG bullet to match. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
dc58e7b224 |
fix(snapshot-settings): make folder save explicit and non-destructive (#130)
* fix(snapshot-settings): make folder save explicit and non-destructive * docs(changelog): note snapshot-settings folder-save behavior fix --------- Co-authored-by: kflame99 <261674183+kflame99@users.noreply.github.com> |
||
|
|
3b914d4eaa | Add Comfy Registry download tracker (#127) | ||
|
|
e1f0bfca33 | docs: require confirmation for external repo targets (#125) | ||
|
|
ea188a4c70 | Release v0.3.1: KForge spotlight onboarding (#126) v0.3.1 | ||
|
|
f8f8adf55d |
Use KForge spotlight as onboarding front door (#124)
* docs: use KForge spotlight and refresh guide showcases * docs: address guide showcase review polish * docs: fix auto-save showcase anchor |
||
|
|
70dab39781 |
Release v0.3.0: Snapshot library + Modules + Install-missing + Pages guide (#121)
Headline themes for 0.3.0: - **Snapshot library** — save/load full Kforge Labs state to a configurable on-disk path, per-preset autosave subfolders, Quick Save, status dot, in-UI recovery, atomic writes, YES/NO modal on newer auto-save, library folder browser, 24-hour timestamps with relative-recent tier. - **Modules** — tag a saved workflow `module` (or use the new "Save as module" checkbox on selection saves) and left-click splices the cluster into the live canvas instead of replacing it. Subgraph wrapper nodes carry their `definitions.subgraphs` entries on save. Insert pre-flight toast surfaces both pack-miss and subgraph-miss cases in plain English. - **Install missing for picks** — one-click installation of missing nodes via ComfyUI-Manager, with discovery → confirm → progress → result UX. - **Hover preview + group modes** — node mock card on row hover, segmented Repository / Theme grouping toggle in the Nodes section, flatten-on-search, spotlight-on-add, × removability for any visible favorite, theme-classifier rework. - **GitHub Pages guide** — bundled visual onboarding now also live at https://malkuthro.github.io/ComfyUI-Koolook/web/guide/, replacing the previous unmaintained wiki link in pyproject.toml's Documentation URL. - **Doc accuracy + polish** — refreshed forks/READMEs (v1_0_1 → v2_3_3), retired stale copilot-instructions, fixed namespace-suffix bullet in THIRD_PARTY.md. Tracked in #120. Full detail in CHANGELOG.md `[0.3.0]`. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>v0.3.0 |
||
|
|
8bbedfe3f7 |
docs: host visual guide on GitHub Pages, retire wiki URL (#117)
The bundled visual sidebar guide is now also live at https://malkuthro.github.io/ComfyUI-Koolook/web/guide/ — pulled from main via GitHub Pages (legacy mode, source=main/, enabled out-of-band via the repos/.../pages API). README's "get started" and "Learn More" sections, plus pyproject.toml's Documentation URL (used by the Comfy Registry package page), now point at the Pages site instead of the unmaintained wiki. The in-app Tools → Help (`H`) button continues to open the bundled local copy via `import.meta.url`, so the guide still works offline. While here, fixed README's stale "Tools → Info" wording — the actual button has the H help glyph and a tooltip reading "Open the Kforge Labs visual guide" (verified in web/sidebar/tree.js). Closes the wiki-URL part of #114; the rest of #114 (cross-ref polish, glossary, snapshot-schema reference page) stays open. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
111ca6d40d |
fix(sidebar): resolve placeholder-drop Promise on every cancel path (#116)
`dropPlaceholdersForPacks` wraps `showConfirmModal` in `new Promise`, but only the OK path resolved — Cancel, Escape, and click-outside left the Promise pending forever (#84 part 2). Caller's `await` never returned; any future UI state tied to the result would have been stuck. Two-layered fix so the wider modal contract is sound, not just the one call site: - `showConfirmModal` (web/sidebar/modals.js) now fires `onCancel` for the Cancel button, Escape, AND overlay-click. An idempotent `settled` flag keeps the callback to a single fire; the OK path flips the flag before tearing down the overlay so it doesn't trigger onCancel via the wrapped `overlay.remove`. Previously only the Cancel button ran the callback — Esc and click-outside silently bypassed it. - `dropPlaceholdersForPacks` (web/sidebar/canvas_io.js) now passes `onCancel: () => resolve({ ok: false, reason: "cancelled" })`, matching the function's other failure shapes (`no-packs`, `load-failed`). Side win: same modals.js change closes a latent Esc/click-outside leak in the existing "Discard offline copy" recovery toast at `web/koolook_sidebar.js:130`, which had wired `onCancel` for the Cancel button only. Refs #84 (part 2 — part 1, stale serialized link cleanup, is on codex/pr-cleanups). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1bde57bf4e |
docs: fix stale fork READMEs + replace outdated copilot-instructions (#115)
* docs: fix stale fork READMEs + retire outdated copilot-instructions forks/README.md and forks/radiance_koolook/README.md both still pointed at v1_0_1 as the active version — v1_0_1 was removed in v0.1.5; the only active fork is v2_3_3. Layout examples and the Upgrade Flow Example pivot forward (v2_3_3 → hypothetical v2_4_0), with a note about SKIP_VERSION_SUFFIX for stable user-facing IDs across upgrades. .github/copilot-instructions.md was severely out of date — referenced a nonexistent k_easy_version.py, knew nothing about the Kforge Labs sidebar (now the main feature), and had no mention of koolook_routes.py, dev-sync, or the three-axis versioning model. Replaced with a thin pointer to CLAUDE.md plus the high-stability orientation Copilot needs to place a request before following links — file map, "things that bite", redirect. Nice-to-have follow-ups filed as #113 (markdown mirror of the visual sidebar guide) and #114 (small cross-ref / glossary polish). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(forks): fix v2.3.3 namespace-suffix bullet in THIRD_PARTY.md The bullet claimed all exposed v2.3.3 node IDs are namespaced with `__koolook_v2_3_3`, but both currently-registered IDs (`Easy_hdr_VAE_encode`, `Easy_hdr_VAE_decode`) are Koolook-original names that opt out via `SKIP_VERSION_SUFFIX` in versions/v2_3_3/__init__.py. The new wording names the actual exposed IDs, points at the opt-out set, and explains the reserved use of the `__koolook_v2_3_3` suffix for future ports of upstream-named classes. Caught in PR #115 review — the linked target (THIRD_PARTY.md) was contradicting the new forks/README.md and CLAUDE.md text on the same mechanism. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
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> |