Files
malkuthro-ComfyUI-Koolook/web/sidebar/picks_store.js
T
malkuthroandClaude Opus 4.7 a0a8469448 feat(sidebar): build-tag footer + theme auto-pull + × removability + pack badges (#98)
Five user-visible changes to the Kforge Labs sidebar that complete the
dev-sync verification loop and fix theme-mode discovery:

- Build-tag footer at the bottom of the sidebar: `dev <sha> · <time>`
  on line 1 (SHA at 13px monospace, the at-a-glance identifier),
  italic <scope> on line 2. Reads `web/_dev_build.json` written by
  `scripts/sync_to_dev.py --scope "<short scope>"`. Visible-in-app
  companion to PR #95's chat-report convention — closes the gap when
  the maintainer is juggling parallel sessions and can't tell whether
  the browser tab is showing the latest sync. Absent on registry
  installs; footer renders empty there.

- Theme/category mode now mirrors repo-mode population: REPOS auto-
  pull union user-picks, deduped, with the new auto-pull-hidden set
  filtered out. The earlier picks-only design dropped the entire
  Koolook pack from theme mode on a stock install, even though those
  same nodes appeared in repo mode — silently violating the "my
  favorites in two views" mental model. `gatherNodesByTheme` rewritten
  to seed from REPOS first, then user picks, with the same
  autoCategoryRoots + hidden filtering repo mode uses.

- `×` works on every favorite, including auto-pulled REPOS nodes.
  Adds `koolook.autoPullHidden.v1` localStorage Set; `×` on an auto-
  pulled row calls `hideAutoPullType(type)` so the next render skips
  it. `addToMyPicks` calls `unhideAutoPullType` as a side effect so
  re-adding via `+` automatically restores visibility. Same uniform
  "remove / bring back" UX whether the node was a user pick or REPOS
  auto-pull.

- Pack-name badges restored on theme-mode leaves only. Small dim
  `Koolook` / `KJNodes` text on the right of each row, same opacity
  as the search-flatten breadcrumb. Memory aid for "where did this
  node come from?" Repo mode and search-flatten still skip the badge
  (origin is conveyed by tree nesting / breadcrumb prefix).

- `scripts/sync_to_dev.py --scope` flag: extends PR #95's brief-
  output design with a JSON artifact (`<target>/web/_dev_build.json`)
  carrying commit + timestamp + worktree + scope, which the sidebar
  footer fetches on render. Doesn't change the existing one-line
  chat-report contract or the `_build_line()` shape.

Tested via dev-sync against the live ComfyUI install:
- Footer renders correctly (`dev afe539e · 2026-05-06 15:38` + scope)
- Image bucket in theme mode now contains Koolook image nodes
- Pack badges visible on theme leaves
- × hides auto-pulled Koolook nodes; toolbar + restores them

CHANGELOG entries added; the prior conflicting [Unreleased] notes
about picks-only theme + pack-badge removal are flagged as superseded
in the new entries (release prep can squash).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-06 16:06:02 +02:00

116 lines
4.2 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SPDX-FileCopyrightText: 2025-2026 Kforge Labs <https://github.com/malkuthro/ComfyUI-Koolook>
// SPDX-License-Identifier: GPL-3.0-only
// =============================================================================
// User picks (localStorage, browser-local persistence)
// =============================================================================
import {
STORAGE_KEY,
PICKS_CHANGED_EVENT,
AUTOPULL_HIDDEN_KEY,
} from "./constants.js";
export function loadUserPicks() {
try {
const raw = localStorage.getItem(STORAGE_KEY);
if (!raw) return [];
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed.filter(x => typeof x === "string") : [];
} catch (e) {
// Surface corruption so the user has a chance to spot it in the console
// before the next saveUserPicks() overwrites the bad blob with a fresh list.
console.warn("[Koolook] failed to parse user picks; returning empty list:", e);
return [];
}
}
function saveUserPicks(picks) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(picks));
return true;
} catch (e) {
console.warn("[Koolook] failed to save picks:", e);
return false;
}
}
// Tri-state: "added" | "duplicate" | "failed". Callers need to distinguish
// "save failed" from "already in list" so the user isn't told a write
// succeeded when localStorage rejected it (quota / private mode).
export function addToMyPicks(typeName) {
if (!typeName) return "failed";
// The `+` action always means "make this visible in favorites." If the
// user previously hid this type via `×` (when it was auto-pulled by a
// REPOS{select: "all"} entry), clear that hide as a side effect — so a
// subsequent gather pass surfaces it again either via picks or via auto-
// pull. Without this, `×` then `+` on the same Koolook node would do
// nothing visible — the hide would silently outlast the re-add.
unhideAutoPullType(typeName);
const picks = loadUserPicks();
if (picks.includes(typeName)) return "duplicate";
picks.push(typeName);
return saveUserPicks(picks) ? "added" : "failed";
}
export function removeFromMyPicks(typeName) {
saveUserPicks(loadUserPicks().filter(p => p !== typeName));
}
// =============================================================================
// Auto-pull hidden set — types the user has explicitly excluded from the
// REPOS{select: "all"} auto-pull. See AUTOPULL_HIDDEN_KEY in constants.js.
// =============================================================================
export function loadAutoPullHidden() {
try {
const raw = localStorage.getItem(AUTOPULL_HIDDEN_KEY);
if (!raw) return new Set();
const parsed = JSON.parse(raw);
return new Set(Array.isArray(parsed) ? parsed.filter(x => typeof x === "string") : []);
} catch (e) {
console.warn("[Koolook] failed to parse autoPullHidden; starting empty:", e);
return new Set();
}
}
function saveAutoPullHidden(set) {
try {
localStorage.setItem(AUTOPULL_HIDDEN_KEY, JSON.stringify([...set]));
return true;
} catch (e) {
console.warn("[Koolook] failed to save autoPullHidden:", e);
return false;
}
}
export function hideAutoPullType(typeName) {
if (!typeName) return;
const set = loadAutoPullHidden();
if (set.has(typeName)) return;
set.add(typeName);
saveAutoPullHidden(set);
}
export function unhideAutoPullType(typeName) {
if (!typeName) return;
const set = loadAutoPullHidden();
if (!set.delete(typeName)) return;
saveAutoPullHidden(set);
}
// Bulk-replace the entire pick list. Used by the snapshot import flow —
// the caller has just deserialized a preset and wants picks to mirror it
// exactly. Filters non-strings defensively (snapshot files are user-
// editable and may have been hand-touched). Returns true on persist
// success, false on localStorage rejection (quota / private mode).
export function setAllPicks(picks) {
const cleaned = Array.isArray(picks)
? picks.filter(p => typeof p === "string")
: [];
return saveUserPicks(cleaned);
}
export function notifyPicksChanged() {
window.dispatchEvent(new CustomEvent(PICKS_CHANGED_EVENT));
}