Commit Graph
12 Commits
Author SHA1 Message Date
ba39cb3658 Claude/goofy faraday d23e98 (#255)
* refactor(nodes): drop keyframe_batch alias from Easy Image Batch

The keyframe_batch input was a deprecated alias of keyframes_insert,
added in 0.4.1 during PR #246 review to keep pre-rename workflows
loading. The old name only ever existed in unreleased dev builds before
the rename, so no published workflow depends on it and the alias only
cluttered the node with a second IMAGE input.

Remove the input, its parameter, and the alias-routing fallback; the
node now exposes a single keyframes_insert insert input. Tests and the
behavior spec updated; CHANGELOG notes the removal under [Unreleased].

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

* docs(policy): make backward compatibility opt-in for Koolook nodes

Invert the default: backward compatibility is no longer a standing
constraint for Koolook-created nodes (the root k_*.py custom nodes).
By default, make the cleanest change — rename/drop/reorder IDs, inputs,
and outputs freely, no deprecation aliases or _v2 suffixes. Compatibility
with saved workflows is only preserved when the maintainer explicitly
says "check backward compatibility".

Forks are explicitly excluded: anything under forks/ (including the
Koolook-original nodes exposed via SKIP_VERSION_SUFFIX) keeps full
back-compat discipline by default, no request needed. The
__koolook_vX_Y_Z collision suffix always applies.

Scoped across the binding instruction files and skills: CLAUDE.md,
copilot-instructions, node-versioning.md, versioning.md, and the
preflight-release / add-external-fork / tdd skills. The preflight
workflows check is reframed as a drift detector for Koolook IDs and a
hard back-compat gate for fork IDs.

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

* docs: clarify keyframe_batch alias removal

* fix: retain keyframe_batch compatibility alias

---------

Co-authored-by: AI Machine <ai.machine@x.baconcph.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 11:16:38 +02:00
malkuthro bda4df576a Release v0.4.1 (#250)
* Release v0.4.0: update footer notice

* Release v0.4.1
2026-06-19 01:38:40 +02:00
88dbfedf86 feat(nodes): Easy Image Batch keyframes_insert + insert-over-source +… (#246)
* feat(nodes): Easy Image Batch keyframes_insert + insert-over-source + clean batch

- Rename optional input keyframe_batch -> keyframes_insert (node ID unchanged).
- Insert-over-source mode: when keyframes_insert AND source_batch are both
  connected, composite the inserts over the source cut window (instead of the
  placeholder background). keyframes_insert alone keeps the offset/reconstruct
  behaviour over placeholder.
- No image source connected -> clean placeholder batch sized by new
  width/height fallback widgets, instead of raising. width/height are appended
  LAST so older saved workflows keep their positional widgets_values alignment
  (fixes a mid-list insertion that shifted placeholder_color into source_frames).
- alpha_batch marks only inserted/placed positions; source background is not
  counted as placed.
- New pure planner plan_source_base_fill (torch-free) alongside
  plan_offset_placements; both unit-tested in CI.
- Docs: behavior matrix spec, interactive HTML demo, and a generated
  "all versions" ComfyUI demo workflow under docs/designs/.

Not yet implemented (specced as DRAFT in docs/designs, pending maintainer
approval + tie-break decision): image1-4 "super-overwrite" layer across all
modes, and removal of the Mode 2 unconnected-slot source_batch fallback.

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

* fix(deps): raise aiohttp [test] floor to >=3.14.1

The constraints lock already pins aiohttp 3.14.1 (the CVE-2026-54273..54280
fix line), but pyproject's [test] extra still declared aiohttp>=3.9.
Dependabot keys off pyproject, not the lock, so the open declared range kept
eight range-based alerts open even though the installed version is patched.
Raising the floor to >=3.14.1 aligns the declared range with the lock and
clears the alerts once this reaches the default branch.

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

* feat(nodes): Easy Image Batch image1-4 super-overwrite + source passthrough

Make the four manual slots the top compositing layer in every mode (select
and insert): a connected imageN overwrites output frame imageN_frame on top
of the sequence/inserts and the background. Higher slot wins a collision
(image4 > image1); a slot beats a sequence pick / insert at the same position.
Extracted as the torch-free plan_slot_overwrites planner + a shared
_apply_slot_overwrites helper used by both code paths.

Two model fixes ride along, per the finalised behaviour matrix:
- An unconnected slot now contributes nothing -- imageN_frame is only a
  placement target for a wired image, never a source_batch pick. The old
  unwired-slot fallback (which surfaced seemingly-random frames) is removed.
- source_batch + an empty frame list (no insert) now passes the source cut
  window straight through instead of a placeholder batch, matching the
  insert-mode empty-list passthrough.

Tests: new plan_slot_overwrites planner cases + fake-tensor behaviour tests
covering slot-beats-pick/insert, higher-slot-wins, passthrough-not-placed, and
the removed phantom fallback. Docstring, tooltips, user guide, design spec
(promoted DRAFT -> canonical), and CHANGELOG updated.

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

* test(nodes): assert selected_image_batch = picks/inserts + slots; drop matrix HTML demo

selected_image_batch already packs every placed position -- both the
source_frames picks / inserts and the manual-slot overwrites, with slots
winning on overlap. Add explicit select- and insert-mode tests asserting the
composition and ascending order, locking the behaviour in.

Also delete docs/designs/easy-image-batch-matrix.html (stale interactive demo
built against an earlier draft) and drop its companion reference from the
design spec.

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

* feat(nodes): Easy Image Batch passthrough inpaint mask + frame-range syntax

Two refinements to the Easy Image Batch passthrough/list behaviour:

- Short-source passthrough is now an extend/inpaint mask. When source_batch is
  connected and the frame list is empty, the covered source frames are kept
  content (alpha 0.0) and the uncovered tail becomes the selection
  (selected_image_batch / selected_frames) with alpha 1.0 -- "keep the first N,
  generate the rest". Full coverage -> no gap, empty selection. The selection
  is the gap here (inverse of select mode); insert mode is unchanged.
- The source_frames field accepts inclusive ranges: "1-5, 7, 9, 14-17" expands
  to 1,2,3,4,5,7,9,14,15,16,17, in both select and insert, via a shared
  torch-free parse_frame_tokens (descending/non-integer tokens warn).

Tests cover the gap selection (short/full/cut-start-offset/slot-in-gap), the
parser (ranges, bad tokens), and range syntax end-to-end. Docstring, tooltip,
user guide, design spec, and CHANGELOG updated.

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

* docs(designs): remove draft-era Easy Image Batch demo workflow + builder

Delete easy-image-batch-demo-setup.json and build_easy_image_batch_demo.py
(the leftover draft-era demo, sibling to the already-removed matrix HTML) and
drop the companion reference from the behavior spec. A purpose-built demo will
replace them later; the spec matrix + the node remain the canonical reference.
Also drop the now-stale "(proposed)" from the spec title (it is canonical).

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

* docs(user-guide): remove stale v0.1.5 Easy Image Batch screenshot

The screenshot predates the node's keyframes_insert / super-overwrite /
passthrough rework and is unreferenced. Removing it to be re-created fresh.

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

* docs: point Easy Image Batch docs at the bundled Select-and-Rebuild demo

Note the runnable demo workflow ("Easy Image Batch - Select and Rebuild",
bundled into the Kforge Labs Workflows sidebar folder via #248) in both the
user guide and the design spec, replacing the generated demo removed earlier.
Referenced by path (not a clickable link) since the workflow lands via #248.

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

* fix(nodes): address PR review — keyframe_batch alias + input guards

- Restore keyframe_batch as a deprecated alias of keyframes_insert so
  pre-rename workflows (origin/main exposes the input) keep loading; an explicit
  keyframes_insert wins. Follows docs/maintainers/node-versioning.md
  (alias-then-deprecate); removal deferred to a release boundary.
- Warn instead of silently returning a clean batch when source_frames is
  non-empty but no image source is connected.
- Cap frame-list range expansion at 8192 frames/token to guard against typos
  like 1-99999999.
- Docs: reframe the bundled demo-workflow note as forthcoming (the JSON lands
  via #248, not on this branch); document the alias, cap, and no-source guard in
  the user guide / design spec / CHANGELOG.

Tests: alias routing + precedence, no-source warning, range cap + boundary
(46 node tests). Fresh-eyes review: no correctness/compat issues (the IMAGE
alias input adds no widget-position shift).

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

* docs: point Easy Image Batch docs at the now-merged Select-and-Rebuild demo

#248 merged to main and is now in this branch (via the main merge), so the
demo workflow exists at docs/workflows/kforge-labs-workflows/. Repoint the user
guide and design spec at it directly (present tense, real folder link) instead
of the earlier "coming soon / pending" framing.

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

---------

Co-authored-by: AI Machine <ai.machine@x.baconcph.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 00:51:16 +02:00
malkuthroandClaude Opus 4.8 ae2480eee9 feat(setups): publish success feedback + published-setup discoverability (#227) (#239)
* feat(setups): publish success feedback + published-setup discoverability (#227)

Post-publish feedback (issue #227 Part 1):
- POST /koolook/api/setups now returns the on-disk storagePath
- new POST /koolook/api/setups/reveal opens the published-setups folder
  (shared _open_dir_in_file_manager helper; the snapshot reveal route was
  refactored onto it, behaviour-identical)
- the publish modal renders a success card reusing the snapshot Save dialog's
  "Saved to ... Open folder" language (Open folder / Copy path / Close)
  instead of closing silently; a draft publish still gets a confirmation

Published discoverability (reuses the tag system, no new schema):
- publishing auto-tags the source workflow `published` -> a "published" badge
  in the Workflows tree + the Tags-section "published" pool
- a new "P" toggle in the sidebar Tools row prunes the Workflows tree to
  published-only, folders force-expanded, so setups show in their origin
  structure; toggling off restores the full tree
- workflowHasTag() shared helper (unit-tested)

Deferred, still open on #227: separate destination/library setting (Part 2),
relocating the storage folder beside the snapshot library, and clearing the
filter when entering Compare mode.

Tests: +server (storagePath, reveal route happy + 404), +client (reveal
client, buildPublishSuccessView view-model), +sidebar (workflowHasTag).
Full suite: 610 passed, 1 skipped.

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

* feat(setups): store published setups beside the snapshot library (#227)

- the published-setup registry path now derives from the configured snapshot
  library (`_configured_dir().parent / koolook-published-setups/setups.json`)
  instead of a fixed user-dir folder, so it follows libraryPath /
  KFORGELABS_PRESETS. The default config resolves to the same path as before
  (target == legacy), so existing default installs are unchanged.
- non-destructive one-time migration: copy a legacy user-dir registry to the
  new location when the new one is absent, leaving the original in place.
- sidebar: clear the "P" published filter when entering Compare mode so it
  doesn't render both compare columns filtered with no toggle to undo it.

Tests: +tests/server/test_published_setups_relocation.py (path beside library;
migration copy / no-op-when-present / no-op-when-no-legacy). Full suite: 614
passed, 1 skipped.

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

* style(tests): remove unused pathlib.Path import (ruff F401)

The relocation test only uses the `tmp_path` fixture, not `Path` directly, so
`from pathlib import Path` was unused and failed the Lint & Smoke (ruff) gate.

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

* fix(setups): address PR #239 review — migration masking + tag persist

- HIGH: a failed legacy->new migration no longer masks real setups with the
  bundled sample. The default registry keeps the legacy file as its primary
  storage when migration could not produce the new file, so stranded data
  stays visible. (+2 tests: keeps-legacy-on-copy-failure / uses-new-on-success)
- MEDIUM/LOW: the publish flow now awaits the `published`-tag persist so the
  success card reflects a committed tag (persist failures already surface via
  persistMutation's critical toast), and warns when the source workflow could
  not be tagged (vanished mid-publish) rather than on a harmless republish.

Full suite: 616 passed, 1 skipped.

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

* fix(setups): harden migration against partial/corrupt relocation target

Addresses the remaining HIGH from the PR #239 consolidated re-review: a
zero-byte/partial/corrupt setups.json at the relocated path could shadow the
intact legacy registry (existence was treated as proof of migration).

- migration now copies via temp file + atomic os.replace (the same pattern
  FileSetupStorage.save_setups uses), so an interrupted copy can never leave
  a partial setups.json at the target; failed-copy temp files are cleaned up.
- primary selection is corruption-aware: when the relocated file exists but
  does not parse as a setups document while the legacy file is intact, the
  registry keeps serving the legacy file and logs a loud diagnostic. The
  unreadable file is left untouched (never overwritten) as evidence. A valid
  relocated file stays authoritative; if both are corrupt, the relocated path
  stays primary so storage diagnostics point at the current location.

Tests (+4): corrupt-target-prefers-intact-legacy (file left untouched),
valid-target-stays-authoritative, both-corrupt-keeps-target, and
interrupted-copy-leaves-no-partial-target-and-no-tmp-litter.

Full suite: 620 passed, 1 skipped.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 23:54:12 +02:00
malkuthro c3c34f0171 Capture API prompt when publishing setups (#233)
* feat(sidebar): capture API prompt when publishing setups

* feat(setups): preview and load canonical setup records

* fix(setups): close publish review gaps
2026-06-08 20:28:35 +02:00
malkuthro 3b35e6a195 feat(setups): infer setup surface from workflow groups (#222)
* feat(setups): infer setup surface from workflow groups

* fix(setups): validate inferred setup surfaces on read

* fix(setups): reject boolean setup geometry
2026-06-07 14:45:16 +02:00
malkuthro 16023662e6 feat(setups): publish sidebar workflows (#216) 2026-06-07 11:07:52 +02:00
malkuthro 7f8c09ad29 feat(sidebar): clean up workflow archives (#201) 2026-06-02 22:25:09 +02:00
malkuthro 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
2026-05-15 10:35:13 +02:00
malkuthroandClaude Opus 4.7 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>
2026-05-14 21:00:34 +02:00
malkuthroandClaude Opus 4.7 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>
2026-05-14 17:43:55 +02:00
malkuthro b185118950 feat(sidebar): refresh action icons (#108)
* feat(sidebar): refresh action icons

* fix(sidebar): label glyph toolbar buttons
2026-05-08 13:10:35 +02:00