* 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>
Visual harness for snapshot dialogs redesign (issue #137)
In-tree, dev-only harness pages that mount real web/sidebar/ modules
in isolation so the agent (or maintainer) can verify each redesigned
dialog renders correctly against docs/designs/snapshot-dialogs.html
without needing a live ComfyUI install.
Status: development-only. Not loaded by ComfyUI; not shipped to the
Comfy Registry archive (the registry scanner walks __init__.py-rooted
exports and WEB_DIRECTORY = "./web", neither of which touches
docs/designs/_harness/).
How it works
Each harness page is a standalone HTML file that:
- Calls
ensureStyle()fromweb/sidebar/constants.jsto inject the live sidebar CSS into the harness document. - Imports the real component under test from
web/sidebar/modals.js(or wherever it lives) via ES-module imports — no copying, no redefining. What the harness renders is what ComfyUI renders. - Stubs only the integration surface — the
getSettings/browseDirectories/createBrowseDirectorycapability functions that the dialog receives as constructor args. The stubs use canned filesystem-shaped data so navigation, new-folder, etc. all work end-to-end inside the harness without a Python server beyond the static file host. - Exposes manual triggers (buttons in the harness header) for the states the matching mockup card illustrates (default, expanded, new-folder-input, error, …). The agent screenshots each state and diffs against the corresponding mockup card.
Running
From the worktree root, start the static file server:
python3 -m http.server 8765
…or, when running under the Claude Code harness, the equivalent
design-server preset in .claude/launch.json
is launched via the preview tool.
Then open one of the harness pages alongside the mockup:
- Folder picker (mockup §6): http://127.0.0.1:8765/docs/designs/_harness/folder-picker.html
- Mockup reference (§6): http://127.0.0.1:8765/docs/designs/snapshot-dialogs.html#
- Archive cleanup: http://127.0.0.1:8765/docs/designs/_harness/archive-cleanup.html
- Publish setup dialog: http://127.0.0.1:8765/docs/designs/_harness/publish-setup.html
archive-cleanup.html is not tied to a design mockup; it mounts the real
sidebar panel against canned workflow-store data so Archive row labels and
the Archive-folder context menu can be screenshot-checked without a live
ComfyUI install.
The harness pages auto-trigger the default state on load; the buttons in the header cycle through the other states the mockup illustrates.
When to add a new harness page
Whenever a commit's assertion lives at the rendered surface (CSS,
layout, wrapper structure) and the matching docs/designs/ mockup
card has more than one state. One harness page per dialog component is
enough — additional states are buttons inside the same page, not
separate files.
When NOT to use this
This is component-level verification — it confirms the redesigned dialog renders against the mockup. It does not replace the integration path:
- Live ComfyUI behaviour (snapshot save round-trip, server
endpoints under real load, theme variables provided by Comfy's
frontend) → still verified via
dev-syncagainst a live install, perdocs/maintainers/dev-iteration-loop.md. - End-to-end flow across multiple dialogs (Save → Load round-trip
picking up a file written in the previous step) → still verified via
dev-sync.
Use the harness to confirm visual parity with the mockup; use
dev-sync to confirm integration with the rest of the sidebar.