main
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
8058e17456 |
fix(snapshot): paired drift-corruption fix — duplicate-install guard + boot-time mismatch (#161, #162) (#186)
* fix(snapshot): prevent drift corruption from duplicate installs + boot-time mismatch (#161, #162) Closes #162 and #161 as the paired fix the issues describe — both touch the same data-loss surface and verify against the same dev-sync rig. installs at import time, log critical, register nothing in the alphabetical loser; client-side `window` sentinel as belt-and-suspenders; align .env.example / dev-iteration-loop / sync_to_dev / README at the Registry-derived `koolook/` path. compare against live /userdata + picks, set a drift flag on mismatch; gate periodic + pre-load autosave routing to `_unsaved_autosave/` while drifted; promote status pill to "drifted" precedence over "saved" with a recovery tooltip; auto-clear on the next markStateSaved (Save / Load). 26 new tests (14 install guard, 4 sidebar sentinel, 8 boot drift) — 316 total passing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(snapshot): apply review fixes — canonical fingerprint + early loser exit + drift timestamp Review-driven follow-ups on PR #186: 1. **Symmetrize file-side fingerprint (MEDIUM).** `detectBootDrift` now uses a new `_canonicalStateFingerprint()` that normalizes the workflows tree through `normalizeWorkflowsStore` AND deep-sorts keys via `sortJsonValue`. Without this, hand-edited / cross-machine / pre-v0.3 files would false-positive as drifted on first boot (e.g. `archived: "false"` string vs `false` bool, missing `directories: {}`, key insertion-order asymmetry from the `{...wf, archived, module, tags}` spread). `_computeFingerprint` stays unchanged so the localStorage save baseline contract holds for existing users. New regression test verifies a shape-only diff does NOT trigger drift. 2. **Gate fork imports on `_is_winning_install` (LOW).** Non-winning duplicate installs now short-circuit before the heavy torch / PIL / diffusers / aiohttp imports, exiting in ~5 LoC instead of paying full import cost just to discard the mappings. 3. **Drift timestamp uses `driftDetectedAt`, not stale `lastNamedSaveAt` (LOW).** The localStorage save baseline could be from a prior session whose live state was already corrupt — surfacing it as "Last named save" in the drift tooltip would mislead. Replaced with the session-noticed-at timestamp from `_bootDriftDiagnostics`. 352 tests passing (316 prior + 26 from PR #186 + 1 new normalize- symmetry case). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(install-guard): fail safe on unreadable / non-UTF-8 siblings (#162) Re-review follow-up. The import-time duplicate-install scan could crash the entire plugin load (fail-silent — the alphabetical winner vanished too, not just the loser) on two unguarded paths: - detect_duplicate_koolook_installs: the per-entry is_dir()/is_file() over custom_nodes/ siblings was unguarded, so a non-traversable sibling (restrictive perms, broken mount, a special dir) raised PermissionError up into __init__.py. No duplicate even required. Now caught per-entry and skipped. - read_pyproject_version: read_text(encoding="utf-8") raises UnicodeDecodeError (a ValueError, NOT OSError) on a non-UTF-8 / binary pyproject.toml, escaping the `except OSError`. Now degrades to "?" via `except (OSError, ValueError)`. - __init__.py: wrapped the guard call site as a final backstop — any unforeseen guard failure now degrades to register-normally rather than aborting the import. Both paths reproduced before the fix; the first surfaced while running the suite from a /tmp checkout whose parent held a root-owned dir. 3 regression tests added (non-traversable sibling skipped; non-UTF-8 pyproject -> "?"; report survives a non-UTF-8 sibling). Full suite: 452 passed (449 + 3). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Co-authored-by: kflame99 <261674183+kflame99@users.noreply.github.com> |
||
|
|
1d9d40ab70 |
Add audio transcript timeline node (#189)
* Add audio transcript timeline node * Hook audio transcripts into Koolook Director * Add modular timeline audio timing workflow * fix(frontend): guard Comfy workflow draft cache * fix(audio): compose transcript timeline prompts * fix(audio): address transcript review findings * fix(ci): keep transcript tests lightweight * fix(timeline): restore image previews from saved paths * fix(timeline): scrub restored preview blobs |
||
|
|
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>
|
||
|
|
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> |
||
|
|
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> |
||
|
|
0d4c81295b |
chore(sync): --init flag for first-run dev-sync setup + Mac path docs (#61)
Background
----------
Setting up `scripts/sync_to_dev.py` on a fresh machine required two
pre-steps the script didn't help with: (1) editing `.env` to point at
a path that didn't exist yet, then (2) `mkdir -p` on that path before
the first sync would work. The script errored out cleanly with
"target does not exist" but didn't tell the user the obvious next
move was to create it. This was particularly silent on Mac where the
ComfyUI install path differs from the Windows examples in `.env.example`.
Changes
-------
- **`--init` flag** on `scripts/sync_to_dev.py`. When the target
directory doesn't exist and `--init` is passed, the script creates
it and proceeds with the sync. Without `--init`, it errors out with
the same "first time on this machine?" hint pointing at the new flag.
- **Safety guards** on `--init`. Refuses (exit 3) if:
- the parent directory doesn't exist, OR
- the parent doesn't look like a ComfyUI `custom_nodes/` folder
(heuristic: parent is named `custom_nodes` OR sits inside a
directory named `ComfyUI`).
This prevents a typo in `KOLOOK_COMFYUI_DEV_PATH` from quietly
creating a `ComfyUI-Koolook/` somewhere unexpected on disk. The
refusal message includes a manual escape hatch (`mkdir -p` + retry
without `--init`) for the case where the parent really IS the
ComfyUI install but doesn't match the heuristic.
- **Drop stale `k_easy_version.py`** from `RUNTIME_PATHS`. The file
was deleted in v0.1.10 (PR #41); the script's `if not src.exists()`
guard meant it was a no-op every run, but the entry was misleading.
- **`.env.example`** gains explicit macOS / Linux / Windows path
examples for `KOLOOK_COMFYUI_DEV_PATH` and a 3-step first-run
recipe (cp .env.example → edit → `--init`).
- **`docs/maintainers/dev-iteration-loop.md`** gains a "First-time
setup on a new machine" section with the per-OS path table and the
same 3-step recipe.
Verification
------------
- `python scripts/sync_to_dev.py --dry-run` against a missing target
→ exit 2, hint at `--init`.
- `python scripts/sync_to_dev.py --init` against a real ComfyUI
`custom_nodes/` parent → creates the leaf, copies 9 entries, exits 0.
- `python scripts/sync_to_dev.py` (no flag) re-run after `--init` →
re-syncs cleanly, idempotent.
- `python scripts/sync_to_dev.py --init` against `/tmp/not-custom-nodes`
→ exit 3, "parent does not exist".
- `python scripts/sync_to_dev.py --init` against an existing
`/tmp/wrong-parent` (not custom_nodes) → exit 3, "doesn't look like
a ComfyUI custom_nodes/ folder", manual escape hatch in the message.
- `python -c "import ast; ast.parse(open('scripts/sync_to_dev.py').read())"`
→ syntax OK.
- `python tools/preflight_release.py --skip manager-meta -v` → PASS.
No CHANGELOG entry — dev-tooling under `scripts/` doesn't ship to
users of the published package. Avoiding noise on the [Unreleased]
section that PRs #59 / #60 also touch.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
e03523faf9 |
refactor(web): split koolook_sidebar.js into 7 sidebar modules (#51)
* refactor(web): split koolook_sidebar.js into 7 sidebar modules Pure mechanical split — zero behavior change. Implements the design from issue #49 (common-case layout with patchCanvasMenu extracted). Old: web/koolook_sidebar.js (1973 lines, monolithic) New: web/koolook_sidebar.js (48-line entry) + 7 modules in web/sidebar/ - constants.js (130) — REPOS, TAB_*, storage keys, event names, URLs, ensureStyle, toast, compareNames - picks_store.js (117) — localStorage user picks: load/save/add (tri-state) /remove/notify/seed/export - workflows_store.js (347) — workflowsCache + persistMutation + snapshotCache + commit + persist*/load*/seed* + in-memory ops; cache is module-private - canvas_io.js (192) — serialize*/canvasIsNonEmpty/loadWorkflowOntoCanvas /insertNode/getSelectedNodeTypes - modals.js (384) — makeModalShell + showInputModal/showConfirmModal /showSaveWorkflowModal/showContextMenu - canvas_menu.js (45) — patchCanvasMenu (idempotent LGraphCanvas patch) - tree.js (825) — pathStates (module-private) + gatherers + DOM row factories + buildFolder + workflow/dir context menus + rebuildTree + renderPanel Encapsulation invariants verified: - workflowsCache: zero references outside workflows_store.js - pathStates: zero references outside tree.js - All 8 files pass node --check - CSS string byte-identical to original - URL constants resolve to same targets after the extra `..` adjustment Documentation updated: dev-iteration-loop.md and workflows-sidebar.md now point pathStates references at web/sidebar/tree.js. Other doc references to koolook_sidebar.js as the entry remain accurate. Closes #49. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(web): drop unreachable defensive guard in gatherWorkflows Review feedback on #51: the `if (!dir) continue;` after `dirOf(dirName)` is unreachable — `listDirectoryNames()` enumerates the same module-private `workflowsCache.directories` keys that `dirOf()` reads, synchronously, within a single event-loop turn. Removing the guard restores strict "byte-identical move" semantics for the file-split refactor. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b7b34de7e2 |
feat(sidebar): curated nodes + workflows tab with archive (#45)
* feat(sidebar): add curated nodes sidebar tab with favorites New "Curated Nodes" sidebar tab. Shows Koolook nodes auto-grouped by subcategory under a "Nodes (favorites)" root. Users add favorites from any installed pack from inside ComfyUI via: - "+" button: adds the currently selected canvas node(s) - Right-click any canvas node -> "Add to Curated Sidebar" Picks persist in browser localStorage (per-browser, survives ComfyUI restarts). Hover any entry -> "x" to remove. Search filter auto-expands all folders so matches stay visible; pack/category folders are collapsed by default otherwise to keep the panel compact. "Download" export button copies current picks as JSON suitable for web/curated_defaults.json -- committed to ship as the package's defaults. On first ComfyUI load with empty localStorage, the seeder reads curated_defaults.json into localStorage so fresh installs get the curated set pre-loaded. Existing users keep their localStorage untouched. Pure JS, no build step. Uses the documented app.extensionManager.registerSidebarTab API (stable since ComfyUI_frontend v1.2.4). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(sidebar): document curated-defaults workflow + seed initial 9 picks Adds docs/maintainers/curated-sidebar.md covering the full Export -> paste -> commit flow for shipping distributed favorites with the package, including storage-key reference, reset procedure, and the intentional "seed once per browser" semantics. CLAUDE.md gets a short pointer so the trigger ("update curated defaults" or pasting an exported picks JSON) is discoverable on the agent side too. Maintainer docs index linked. Seeds web/curated_defaults.json with the maintainer's first 9 picks (GetImageSizeAndCount, GetNode, ImageScaleBy, LoadEXR, NukeConstant, NukeOCIOColorSpace, SaveEXRFrames, Text Multiline, easy showAnything). Fresh installs from this version forward will get them pre-loaded into their localStorage on first ComfyUI launch; existing users' picks are untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs(sidebar): trim curated-sidebar.md to a one-screen quick reference Drops the verbose data-flow diagram and the localStorage-vs-userdata rationale. Keeps the four action steps, the storage-key map, the reset block, and the four "things that surprised us" footnotes capturing the non-obvious bits (paste is manual, other users can't reach the repo, seed-once-per-browser, no publish on web/ pushes). 81 lines removed, 20 added; same file path so existing links still resolve. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(sidebar): workflows section with archive + iteration docs Adds a Workflows section to the Curated Nodes sidebar tab, sharing the search field and tree pattern with the existing nodes feature. Capabilities: - Save entire canvas or current selection as a workflow. - Save modal: New name / Use existing name (archive previous) / Modify existing name. The Workflow-name field is shown only when needed; "Base on existing" dropdown drives prefill for Use/Modify. - Auto-archive on same-name save: previous version renamed with timestamp suffix and flagged archived; new entry takes the slot. Archive sub-folder appears in each directory only when populated and is rendered above the active list, so the latest sits at the bottom. - Right-click on workflow: Load / Rename / Move to other directory / Move to archive (or Restore from archive) / Delete. - Right-click on directory: Rename / Delete (with confirm if non-empty). - Loading a workflow names the tab via loadGraphData's 4th arg (verified against ComfyUI_frontend src/scripts/app.ts) so the tab flips from "Unsaved Workflow (N)" to the saved name. Storage: ComfyUI /userdata/koolook_workflows.json (primary), with a localStorage fallback if /userdata is unreachable. Distributed defaults seeded from web/workflow_defaults.json on first ComfyUI load with empty workflow data -- same one-time-per-install pattern as curated_defaults.json. Other panel changes: - Three labeled rows above the tree (search; NODES action bar; WORKFLOWS action bar), with thin dividers between them and before the tree. - Folder expansion state preserved across re-renders via a pathStates Map; saving never collapses the directory you were viewing. - Alphabetical (case-insensitive) sort everywhere via a single compareNames helper -- pack names, subcategories, directories, workflow names, dropdown options. - "Use existing name (archive previous)" action label includes the parenthetical hint about archive behavior. Docs: - New docs/maintainers/dev-iteration-loop.md captures the dev-sync->restart->verify pattern, agent trigger phrases, persistence boundaries, and push/publish gates. First read when returning to UI work after a break. - New docs/maintainers/workflows-sidebar.md covers workflow storage, action semantics, archive behavior, and reset procedure. - docs/maintainers/README.md indexes both. - CLAUDE.md "dev-sync" section now points at the iteration-loop doc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sidebar): address review findings — seed gating + commit checks Targets the data-integrity findings from the multi-agent PR review: - BLOCKING: seed flag is now only set after the underlying save/persist succeeded. `saveUserPicks` returns a boolean; both seeders bail out (leaving the flag unset for next-load retry) on storage failure. Previously a transient storage failure during first-load could permanently strand a user with an empty store and the seeded flag set. - BLOCKING: `normalizeWorkflowsStore` now strictly validates each workflow entry — drops entries missing a graph object, coerces `archived` to a strict boolean (so a stray "false" string can't accidentally hide a workflow), and logs the dropped count. - HIGH: save handlers now check `commit()`'s return value and surface a "Save failed" toast when both /userdata and the localStorage fallback rejected the write. Previously the user saw "Saved" even though nothing was persisted. The canvas and selection handlers share a `saveAndToast` helper to keep the failure path consistent. - HIGH: `saveCanvasBtn` now checks `canvasIsNonEmpty()` before serializing. A `serialize()` throw is no longer misreported as "Canvas is empty." — distinct toasts for distinct failure modes. - HIGH: `loadWorkflowsStore` now logs (instead of silently swallowing) parse failures on the localStorage fallback, and warns when /userdata is reachable but a stale fallback blob still exists from an earlier outage — gives the user a recovery breadcrumb. - HIGH: `loadUserPicks` logs parse failures so a corrupt picks blob is visible in the console before the next save overwrites it. Skipped from the review with rationale: - multi-tab seed race: sub-second window, both tabs write identical defaults, no observable wrong state — navigator.locks not worth it - splitting `koolook_sidebar.js` and decomposing `renderPanel`: refactor scope, deferred to follow-up - `loadGraphData` rename comparison: agent's claim was wrong; existing `wf.filename !== wfName` matches the upstream `filename` shape (bare name, no extension) we verified earlier against ComfyUI_frontend v1.44.x source Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sidebar): second-pass review fixes — rollback, idempotency, corrupt-file handling Addresses the residual findings from the post-625aa97 review pass. BLOCKING: - patchCanvasMenu is now idempotent. Adds a __koolookCuratedPatched sentinel on LGraphCanvas.prototype so a soft-refresh / re-import doesn't stack duplicate "Add to Curated Sidebar" entries on every node's right-click menu. (LGraphCanvas is shared with every other pack, so non-idempotent prototype mutation was visible globally.) HIGH: - persistWorkflowsToServer now returns "server" | "fallback" | false instead of just true/false. seedWorkflowDefaultsIfNeeded only marks WORKFLOWS_SEEDED_KEY when the seed actually reached /userdata — previously a fallback-only seed (when /userdata was unreachable on first install) marked the user as "seeded" and they would have an empty server file forever after recovery. - All 8 commit() callsites that mutate workflowsCache (saveAndToast, move, archive, restore, rename workflow, delete workflow, rename directory, delete directory + delete-non-empty-directory, create directory) now go through a new persistMutation helper that: - snapshots the cache before mutation - bails (toast no-op) if mutate returns false - rolls back the cache + re-renders if commit() returns false - toasts persistFailedMessage so the user isn't told a write succeeded when both /userdata and the localStorage fallback rejected it Pairs with snapshotCache() helper for explicit snapshot/restore. - addToMyPicks tri-state ("added" | "duplicate" | "failed") so callers can distinguish "already in favorites" from "save failed" on localStorage quota / private-mode failure. Both call sites (the + toolbar button and the right-click "Add to Curated Sidebar" menu entry) updated to handle all three states. - fetchWorkflowsFromServer now distinguishes a network failure from a parse failure of an existing-but-corrupt /userdata file. Returns SERVER_FILE_CORRUPT (a Symbol) on parse error; loadWorkflowsStore surfaces a toast and returns { corrupt: true }; setup() skips seedWorkflowDefaultsIfNeeded entirely so we don't auto-overwrite a recoverable-but-unreadable file with stock defaults. - loadWorkflowOntoCanvas snapshots the current canvas before app.loadGraphData and restores it on throw. Previously a workflow with a now-unregistered node type could destroy the canvas partway through and leave the user with neither the old graph nor the new. - Stale localStorage fallback detection (when /userdata is reachable but a fallback blob from a previous outage is still present) now surfaces a toast in addition to the console warning, so a user who doesn't have DevTools open gets a recovery breadcrumb. MEDIUM: - Modal escape-handler leak fixed via centralized close(). Every dismissal path (Escape key, overlay click, Cancel/OK calling overlay.remove()) now tears down the document-level keydown listener. Existing call sites that use overlay.remove() keep working — the remove method itself routes through cleanup. Skipped from the review with rationale: - subcategoryFor cosmetic mismatch fallback at L1000-1008 — silent rewrite is rare-path defensive, not load-bearing; defer. - Optional toast on seedDefaultsIfNeeded storage failure — console warning is sufficient for a development-time scenario; defer. - File size / module split (1864 lines) — refactor scope, deferred to follow-up per prior commit message. - Magic sentinel strings ("__new__", action enum strings) — pure cleanup; defer. - dataset.expanded boolean-string compare — pathStates Map already carries booleans; cleanup defer. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |