f98633faedd5ccbc2a98d53dc72622fac61dfd78
8
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
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> |
||
|
|
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> |
||
|
|
96b07a2c12 |
feat(sidebar): picks priority — drag-link menu, spotlight on add, modal hardening (#76)
* fix(sidebar): merge preset HEAD route into GET handler PR #68 shipped koolook_routes.py with @routes.get + @routes.head on the same path. aiohttp's add_get auto-registers HEAD by default, so the explicit @routes.head collided with the auto-synthesized HEAD and crashed ComfyUI startup with "Added route will never be executed, method HEAD is already registered". Trying allow_head=False on the GET to opt out triggered a second crash: ComfyUI's server.py mirrors every PromptServer route into a parallel /api/... table via api_routes.route(method, path)(handler, **kwargs), and RouteTableDef.route's inner closure rejects unknown kwargs with TypeError. So no kwarg-based opt-out is viable. Drop the dedicated HEAD handler. Aiohttp's auto-HEAD-from-GET delivers HEAD probes to get_preset; we branch on request.method to skip the file read for HEAD — preserving the cheap-existence optimization the client's presetExists relies on for multi-MB JSON over Dropbox / iCloud / NFS-mounted libraries. CHANGELOG [Unreleased] updated to match. This bug never reached users — koolook_routes.py was added in #68 (post-v0.2.0) and was never published to Comfy Registry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(sidebar): Tools row + starter preset replaces curated_defaults Two coordinated UX changes for fresh-install distribution + admin ergonomics — designed together and shipped as one unit. 1. New "Tools" row sits between the Snapshot row and the search field. Moves the Export and "Install missing for picks" buttons up out of the Nodes row (admin/advanced operations don't belong in the everyday + Add toolbar). Adds a new "Drop placeholders onto canvas" button — the security_level=normal escape hatch for installing missing packs: instantiates one placeholder per missing pack on a fresh canvas tab so ComfyUI/Manager's standard "Install Missing Custom Nodes" detection takes over via Manager's UI flow (which uses different gates than our programmatic /customnode/install/ git_url call). Nodes row keeps only the everyday + button. 2. curated_defaults.json retired in favor of starter_preset.json. Fresh installs no longer get picks seeded directly into localStorage on first load. Instead the bundled web/starter_preset.json (full snapshot format — picks + workflows + tags + archive) is copied into the user's snapshot library directory as starter.json. The user opens Snapshot -> Load -> starter to apply in one click. Unifies fresh-install distribution with the per-user snapshot library (#68); maintainer flow becomes "build state in ComfyUI -> click the (down-arrow) Tools-row button -> paste over web/starter_preset.json -> commit." Existing users with non-empty picks are explicitly skipped by the new seeder, so their state is untouched. Removed: seedDefaultsIfNeeded, exportPicks, web/curated_defaults.json, SEEDED_KEY, DEFAULTS_URL. Added: seedStarterPresetIfNeeded, exportStarterPreset (snapshot.js), STARTER_SEEDED_KEY, STARTER_URL, STARTER_PRESET_FILENAME, discoverMissingPacks (installer.js), dropPlaceholdersForPacks (canvas_io.js), web/starter_preset.json. Docs: CLAUDE.md "Curated Nodes sidebar — distributed defaults" -> "Kforge Labs sidebar — starter preset distribution". docs/maintainers/curated-sidebar.md rewritten. CHANGELOG.md [Unreleased] updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(sidebar): picks priority — drag-link menu, spotlight on add, modal hardening Three coordinated UX features for curated picks plus two underlying bug fixes uncovered while wiring them. 1. Drag-link-release menu hoists curated picks. When a connection is dragged from a socket and released on empty canvas, ComfyUI's compat- filtered suggestion list now sorts items in the user's curated picks above everything else, with a "── from your picks ──" separator before the rest. Detection via the "Add Node" + "Add Reroute" sentinel pair in the menu's options (narrow enough that other ContextMenu uses are untouched). Purely additive — no compatible nodes are removed, only re-ordered. New `web/sidebar/connection_menu.js` exports a `patchConnectionMenu()` registered alongside `patchCanvasMenu()`. 2. Spotlight effect on add. Clicking the toolbar `+` (or the canvas right-click "Add to Kforge Labs") collapses every Nodes-section pack folder, then auto-expands the pack + subcategory the just-saved node lives in — pedagogical aid that helps users learn which pack each node belongs to. Multi-select adds light up every hit pack simultaneously. Duplicate adds get the spotlight too (the educational reminder still applies). New helper `findPackPathForType` mirrors the gather code's REPO-precedence-then-user-pick-fallback. New exported `spotlightAddedPicks(typeNames)` does the collapse + pin sequence. 3. Modal drag-out-of-input no longer dismisses the dialog. Shared modal shell `makeModalShell` gained a mousedown / click-intent check: a click on the overlay only fires dismiss when the gesture STARTED on the overlay too. Drag-selecting text inside an input that releases in the overlay's dark area used to dismiss the modal mid-edit. Affects every Koolook dialog. Snapshot Settings path field also gained a `title` tooltip so the full path is hover-readable. Bug fixes uncovered + carried in this PR: - `pinExpanded` paths now expand on the immediate render. buildFolder's resolution chain gained a `pinnedPaths` check between `forceExpanded` and `pathStates`. Phase 3 of `renderTree` writes pins into pathStates AFTER Phase 2 builds the DOM, so without this check the pin had no effect on the current render. Improves the existing workflow-save pinning (visibly correct now) and is load-bearing for the new spotlight feature. CHANGELOG [Unreleased] updated with all five entries. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a6a31c8086 |
feat(sidebar): Tools row + starter preset replaces curated_defaults (#72)
* fix(sidebar): merge preset HEAD route into GET handler PR #68 shipped koolook_routes.py with @routes.get + @routes.head on the same path. aiohttp's add_get auto-registers HEAD by default, so the explicit @routes.head collided with the auto-synthesized HEAD and crashed ComfyUI startup with "Added route will never be executed, method HEAD is already registered". Trying allow_head=False on the GET to opt out triggered a second crash: ComfyUI's server.py mirrors every PromptServer route into a parallel /api/... table via api_routes.route(method, path)(handler, **kwargs), and RouteTableDef.route's inner closure rejects unknown kwargs with TypeError. So no kwarg-based opt-out is viable. Drop the dedicated HEAD handler. Aiohttp's auto-HEAD-from-GET delivers HEAD probes to get_preset; we branch on request.method to skip the file read for HEAD — preserving the cheap-existence optimization the client's presetExists relies on for multi-MB JSON over Dropbox / iCloud / NFS-mounted libraries. CHANGELOG [Unreleased] updated to match. This bug never reached users — koolook_routes.py was added in #68 (post-v0.2.0) and was never published to Comfy Registry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * feat(sidebar): Tools row + starter preset replaces curated_defaults Two coordinated UX changes for fresh-install distribution + admin ergonomics — designed together and shipped as one unit. 1. New "Tools" row sits between the Snapshot row and the search field. Moves the Export and "Install missing for picks" buttons up out of the Nodes row (admin/advanced operations don't belong in the everyday + Add toolbar). Adds a new "Drop placeholders onto canvas" button — the security_level=normal escape hatch for installing missing packs: instantiates one placeholder per missing pack on a fresh canvas tab so ComfyUI/Manager's standard "Install Missing Custom Nodes" detection takes over via Manager's UI flow (which uses different gates than our programmatic /customnode/install/ git_url call). Nodes row keeps only the everyday + button. 2. curated_defaults.json retired in favor of starter_preset.json. Fresh installs no longer get picks seeded directly into localStorage on first load. Instead the bundled web/starter_preset.json (full snapshot format — picks + workflows + tags + archive) is copied into the user's snapshot library directory as starter.json. The user opens Snapshot -> Load -> starter to apply in one click. Unifies fresh-install distribution with the per-user snapshot library (#68); maintainer flow becomes "build state in ComfyUI -> click the (down-arrow) Tools-row button -> paste over web/starter_preset.json -> commit." Existing users with non-empty picks are explicitly skipped by the new seeder, so their state is untouched. Removed: seedDefaultsIfNeeded, exportPicks, web/curated_defaults.json, SEEDED_KEY, DEFAULTS_URL. Added: seedStarterPresetIfNeeded, exportStarterPreset (snapshot.js), STARTER_SEEDED_KEY, STARTER_URL, STARTER_PRESET_FILENAME, discoverMissingPacks (installer.js), dropPlaceholdersForPacks (canvas_io.js), web/starter_preset.json. Docs: CLAUDE.md "Curated Nodes sidebar — distributed defaults" -> "Kforge Labs sidebar — starter preset distribution". docs/maintainers/curated-sidebar.md rewritten. CHANGELOG.md [Unreleased] updated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
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> |